mybatis 作为数据的 ORM 框架,在国内的应用市场还是非常可观的,当初刚开始工作时使用 spring + mybatis 进行开发,后来也使用过 hibernate, jdbctemplate, jooq,mybatisplus 等其他的一些框架,
就个人使用感触来讲 jooq 的使用姿势和写 sql 差不多,基本上可以会写 sql 的无需额外的培训,立马可以上手;
hibernate 最大的特点就是借助方法名来映射 sql 语句,非常有特点,但是当查询条件复杂一些的话,对小白而言就没有那么友好了;
而 jdbctemplate,这个在小项目,轻量的 db 操作中,用起来还是很爽的,非常灵活,但是也有一些点需要特别注意,比如 queryForObject,查不到数据时抛异常而不是返回 null;
至于 mybatis 以及衍生的 mybatis-plus,也就是接下来的主角了,它的特点如何,为什么受到国内大量开发者的追捧,将它作为 db 操作的第一 ORM 框架,让我们看完之后再说
I. 基础环境搭建
接下来的 Mybatis 的项目演示,主要是在 SpringBoot 的环境下运行,底层的数据库采用 MySql,对应的版本信息如下
- springboot: 2.2.0.RELEASE
- mysql: 5.7.22
1. SpringBoot 项目配置
关于 SpringBoot 的项目创建过程省略,下面是核心的 pom 依赖
<dependencies> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> </dependencies> 复制代码
核心的依赖mybatis-spring-boot-starter
,至于版本选择,到 mvn 仓库中,找最新的
另外一个不可获取的就是 db 配置信息,appliaction.yml
spring: datasource: url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 复制代码
2. 数据库准备
在本地数据库中,新增了一个表如下
CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=551 DEFAULT CHARSET=utf8mb4; 复制代码
接下来本文涉及到的 CURD 都是针对这张表来说的
II. MyBatis CURD
接下来我们将从 0 到 1,实现基于 mybatis 进行 mysql 操作的全流程
1. 基本对象
经常使用 Mybatis 的小伙伴可能知道,操作一个 db,通常会伴随几个不可或缺的东西
- 数据库实体类:可以理解为数据库表锁映射到的 Java Bean 对象
- Mapper 接口:interface 类,其中定义 db 的操作方法
- xml 文件:与上面接口对应,xml 文件中写实际的 sql
mybatis 推荐的玩法是借助 xml 来写 sql,但是官方也提供了注解的方式,因此 xml 文件并不是必须的;后面会介绍注解的操作方式;本文将主要是传统的 xml 配套使用姿势
针对上面这张表,第一步定义实体类MoneyPo
@Data @NoArgsConstructor @AllArgsConstructor public class MoneyPo { private Integer id; private String name; private Long money; private Integer isDeleted; private Timestamp createAt; private Timestamp updateAt; } 复制代码
- 上面的三个注解属于 lombok 的知识点,有不清楚的小伙伴可以搜索一下
接下来是 Mapper 接口, MoneyMapper
如下
// 注意这个@Mapper注解,用于表明这个接口属于Mybatis的Mapper对象 @Mapper public interface MoneyMapper { } 复制代码
然后是 Mapper 接口对应的 xml 文件MoneyMapper.xml
注意 xml 文件放在资源文件resources
下面,且 xml 文件的目录结构,与上面的 Mapper 接口的包路径保持完全一致 (why? 参看博文 【DB 系列】SpringBoot 系列 Mybatis 之 Mapper 接口与 Sql 绑定几种姿势)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.git.hui.boot.mybatis.mapper.MoneyMapper"> </mapper> 复制代码
2. 数据插入
前面的三步骤,将我们需要的实体类,接口对象,xml 文件都初始化完毕,接下来就是进入我们的 CURD 环节,实现数据库的增删改查,这里主要使用insert
标签
比如我们现在希望插入一条数据,首先需要做的就是在 Mapper 接口中定义一个方法
int savePo(@Param("po") MoneyPo po); 复制代码
接着就是在 xml 文件中对应的 sql
<insert id="savePo" parameterType="com.git.hui.boot.mybatis.entity.MoneyPo" useGeneratedKeys="true" keyProperty="po.id"> INSERT INTO `money` (`name`, `money`, `is_deleted`) VALUES (#{po.name}, #{po.money}, #{po.isDeleted}); </insert> 复制代码
2.1 解析说明
注意上面的 xml 文件
- parameterType: 用于指定传参类型
- useGenerateKeys + keyProperty: 表明需要将插入 db 的主键 id,会写到这个实体类的 id 字段上
- sql 语句传参:形如
#{}
,大括号里面填写变量名,上面用的是po.name
,po 为接口定义中的参数名,这个就表示使用 po 对象的 name 成员,作为 db 的 name 字段
接下来就是重要知识点:
- 传参除了使用
#{}
之外,还可以使用${}
,区别在于前面为参数参数占位,后面为字符串替换,因此存在 sql 注入的风险
举例说明
select * from money where id=${id} select * from money where id=#{id} 复制代码
针对上面这两个 sql,当id = 1 or 1=1
,对应的两个 sql 变成
-- 第一个sql会返回所有的数据 select * from money where id = 1 or 1 =1 -- 下面这个会抛sql异常 select * from money where id = '1 or 1=1' 复制代码
2.2 批量插入
除了上面的单挑插入,批量插入也是 ok 的,和前面的使用姿势差不多
int batchSave(@Param("list") List<MoneyPo> list); 复制代码
对应的 sql 如下
<insert id="batchSave" parameterType="com.git.hui.boot.mybatis.entity.MoneyPo" useGeneratedKeys="true" keyProperty="id"> insert ignore into `money` (`name`, `money`, `is_deleted`) values <foreach collection="list" item="item" index="index" separator=","> (#{item.name}, #{item.money}, #{item.isDeleted}) </foreach> </insert> 复制代码
对于 foreach 标签的说明,会放在后面的博文中专门进行介绍,这里简单理解为遍历即可
3. 数据查询
查询可以说是我们日常开发中最常见的情况了,这里先给出简单的查询 demo,至于更复杂的查询条件(如联表,子查询,条件查询等)在后面的博文中进行介绍
如根据主键进行查询,主要借助select
标签来实现
MoneyPo findById(int id); 复制代码
对应的 sql
<resultMap id="BaseResultMap" type="com.git.hui.boot.mybatis.entity.MoneyPo"> <id column="id" property="id" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <result column="money" property="money" jdbcType="INTEGER"/> <result column="is_deleted" property="isDeleted" jdbcType="TINYINT"/> <result column="create_at" property="createAt" jdbcType="TIMESTAMP"/> <result column="update_at" property="updateAt" jdbcType="TIMESTAMP"/> </resultMap> <sql id="money_po"> id, name, money, is_deleted, create_at, update_at </sql> <select id="findById" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select <include refid="money_po"/> from money where id=#{id} </select> 复制代码
重点关注下上面的实现,select 语句内容比较简单,但是有几个需要注意的点
- sql 标签:内部定义需要查询的 db 字段,最大的特点是供后面的查询语句,通过
include
来引入,从而实现代码片段的复用 - resullMap 标签:从 db 字段与 MoneyPo 实体类对比,我们可以知道部分字段名不是完全一样,如 db 中使用下划线,java 中使用驼峰,那么 db 字段与 java 成员变量如何映射呢?这里使用
result
标签来指定两者的映射关系,以及类型
(上面这个相信会始终伴随各位小伙伴的开发生涯)
4. 数据更新
更新主要借助update
标签,相比较上面的两个,它的知识点就比较少了
int addMoney(@Param("id") int id, @Param("money") int money); 复制代码
对应的 sql 如下
<update id="addMoney" parameterType="java.util.Map"> update money set money=money+#{money} where id=#{id} </update> 复制代码
说明
- 上面标签中的 parameterType,在这里实际上是可以省略的
@Param
注解:主要用于指定参数名,在 xml 中可以使用内部定义的名字来作为参数变量;如果不加上这个注解,在 xml 中,参数变量则使用param0
,param1
来替代
5. 数据删除
删除使用delete
标签
int delPo(@Param("id") int id); 复制代码
对应的 sql 如下
<delete id="delPo" parameterType="java.lang.Integer"> delete from money where id = #{id,jdbcType=INTEGER} </delete> 复制代码
6. 使用演示
上面的 mapper 接口中定义了完整的 CURD,接下来就是使用这个 Mapper 接口来实现交互了,在 Spring 中,使用姿势就非常简单了,直接当一个 Spring Bean 对象注入到 service 类中即可
@Repository public class MoneyRepository { @Autowired private MoneyMapper moneyMapper; public void testBasic() { MoneyPo po = new MoneyPo(); po.setName("mybatis user"); po.setMoney((long) random.nextInt(12343)); po.setIsDeleted(0); moneyMapper.savePo(po); System.out.println(po); MoneyPo out = moneyMapper.findById(po.getId()); System.out.println("query:" + out); moneyMapper.addMoney(po.getId(), 100); System.out.println("after update:" + moneyMapper.findById(po.getId())); moneyMapper.delPo(po.getId()); System.out.println("after del:" + moneyMapper.findById(po.getId())); } } 复制代码
执行输出结果如下
MoneyPo(id=552, name=mybatis user, money=7719, isDeleted=0, createAt=null, updateAt=null) query:MoneyPo(id=552, name=mybatis user, money=7719, isDeleted=0, createAt=2021-08-01 11:47:23.0, updateAt=2021-08-01 11:47:23.0) after update:MoneyPo(id=552, name=mybatis user, money=7819, isDeleted=0, createAt=2021-08-01 11:47:23.0, updateAt=2021-08-01 11:47:23.0) after del:null 复制代码
7. 小结
相信各位小伙伴看到这里,搭建一个 mybatis 实现数据库的 CURD 的项目应该是问题不大了,本文的主要知识点如下
- mybatis 项目的三套件:实体类 + mapper 接口 + xml 文件
- 数据库的增删改查
其中有一些知识点比较重要,本文只是抛出来了,有兴趣的小伙伴可以持续关注后续更新
下面这些知识点,后面会进行更详细的说明
- 如何获取插入数据的主键 id
- 批量场景下的
foreach
标签使用 - 数据库表结构与 java 实体类的映射
resultMap
标签 - Mapper 接口与 xml 文件的关联方式
- Mapper 接口如何被扫描到,并被 Spring bean 对象
- Mapper 接口与 xml 的传参方式
@Param
注解 - sql 参数替换的两种写法
${}, #{}
- 传参类型,返回值类型定义
- 代码复用片段
sql
标签