MyBatis 增删改查案例

准备环境

  • 为了开发更加方便推荐安装 MyBatisX 插件
  • 数据库:准备数据库表 tb_brand
  • pom.xml 文件:已导入 MaBatis 和 MySQL 坐标
  • 已配置好 mybatis-config.xml 配置文件
    • 设置好了 typeAliases 标签
    • 设置好了数据库的连接信息
  • 实体类:在项目下新建 pojo 软件包,创建 Brand 实体类
  • 测试用类:将主程序写在 test 文件夹的项目包下

测试用类及项目结构如下图所示
01-测试用类及项目结构.png

MyBatisX 插件

MyBatisX 是一款基于 IDEA 的快速开发插件,为效率而生

插件的主要功能有:

  1. XML 和接口方法相互跳转
  2. 根据接口方法生成 statement

02-安装MaBatisX插件.png

安装完重启 IDEA,相应的 xml 文件图标就变成小鸟的图标了
红色的小鸟:SQL 的映射文件
蓝色的小鸟:Mapper 的接口

03-蓝色小鸟与红色小鸟.png

查询所有数据

1、编写接口方法:Mapper 接口

  • 参数:无
  • 返回类型:List<Brand>
1
List<Brand> selectAll();

2、编写 Mapper 配置文件

1
2
3
4
<!--查询标签,id为这条SQL语句的唯一标识,resultType为返回结果类型,不区分大小写-->
<select id="selectAll" resultType="brand">
select * from tb_brand;
</select>

04-利用MaBatisx插件自动在配置文件中生成对应标签.png

3、执行方法,测试

MaBatis 完成操作只需要三步

  1. 编写接口方法
  2. 编写 SQL
  3. 执行方法

查询结果中部分字段显示为 null

查询出来的结果中,部分字段显示 NULL,为什么呢?

05-部分字段显示为null.png

因为这些字段,原本在数据库中比如说 brand_name,到了 Java 当中变量名就变成了 brandName(在 POJO 的实体类),数据库表的字段名称和实体类的属性名称不一样,所有就不会为我们自动封装了

解决办法 1

在 SQL 查询语句中为相应的字段设置别名,让别名和实体类的属性名一致即可

原来的 SQL 查询

1
2
3
<select id="selectAll" resultType="brand">  
select * from tb_brand;
</select>

修改后的 SQL 查询

1
2
3
4
<select id="selectAll" resultType="brand">  
select id,brand_name brandName,company_name companyName,ordered,status
from tb_brand
</select>

但是这样每进行一次查询都需要重新设置一次,而且不能复用

解决办法 2

使用 resultMap 标签(最为常用)

06-ResultMap实现字段名和类的属性名一一对应.png

1
2
3
4
5
6
7
8
9
10
11
12
<resultMap id="brandResultMap" type="brand">
<!--id标签完成主键的映射,result标签完成非主键的映射-->
<!--<id></id>-->
<result column="brand_name" property="brandName"></result>
<result column="company_name" property="companyName"></result>
</resultMap>

<select id="selectAll" resultMap="brandResultMap">
select
*
from tb_brand
</select>

resultMap 标签中:

  • id:唯一标识
  • type:映射的类型,支持别名
  • id 子标签:完成主键字段的映射,具有属性 column 表的列名和 property 实体类的属性,以上代码中并没有演示 id 子标签的使用
  • result 子标签:完成一半字段的映射,具有属性 column 表的列名和 property 实体类的属性

注意:select 标签中原先的 resultType="brand" 改为了 resultMap="brandResultMap",因为在 resultMap 标签中 type 属性也定义了类型

根据 id 查询数据

1、编写接口方法:Mapper 接口

  • 参数:id
  • 返回类型:Brand 类
1
2
//传入一个id,最终返回一个Brand类型
Brand selectById(int id);

2、编写 SQL 语句

1
2
3
4
5
<select id="selectById" resultMap="brandResultMap" >
select *
from tb_brand
where id = #{id};
</select>

3、执行方法,测试

只需要修改执行方法这一部分代码,其他地方不需要改动

1
2
3
4
//4.执行方法  
int id = 1;
Brand brand = brandMapper.selectById(id); //返回一个brand对象
System.out.println(brand);

条件查询 - 多条件查询

1、编写接口方法:Mapper 接口

  • 参数:所有查询条件
  • 返回类型:List<Brand>

2、编写 SQL 语句

1
2
3
4
5
6
7
<select id="selectByCondition" resultMap="brandResultMap">
select *
from tb_brand
where status = #{status}
and company_name like #{companyName}
and brand_name like #{brandName};
</select>

注意:以上代码中需输入三个参数才能查出结果,如果某一个参数没有填写就查询不出来,因此需要 SQL 语句动态变化(SQL 语句会随着用户的输入或外部条件的变化而变化)

解决方案 1:在 where 后面添加一个恒等式,然后所有的判断条件语句中都可以加上 and 这个关键词

1
2
3
4
5
6
7
8
9
10
11
12
select *  
from tb_brand
where 1 = 1
<if test="status!=null">
and status = #{status}
</if>
<if test="companyName!=null and companyName!='' ">
and company_name like #{companyName}
</if>
<if test="brandName!=null and brandName!='' ">
and brand_name like #{brandName}
</if>

解决方案 2:利用 MaBatis 提供的 where 标签

这样的话,就不需要管到底用户传递了几个参数过来,会动态判断是否需要添加 and 关键字

1
2
3
4
5
6
7
8
9
10
11
<where>  
<if test="status!=null">
and status = #{status}
</if>
<if test="companyName!=null and companyName!='' ">
and company_name like #{companyName}
</if>
<if test="brandName!=null and brandName!='' ">
and brand_name like #{brandName}
</if>
</where>

3、执行方法

1
2
3
//执行方法
List<Brand> brands = brandMapper.selectByCondition(brand);//传入一个brand对象
System.out.println(brands);

三种接收参数的格式

1. 散装参数格式

1
2
//1.散装参数的格式,需要使用@Param("SQL参数占位符名称")  
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName, @Param("brandName") String brandName);

2. 对象参数

对象的属性名称要和参数占位符一致

1
List<Brand> selectByCondition(Brand brand);

注意:对于对象参数,在处理参数的时候,需要有封装对象的处理

1
2
3
4
5
6
7
8
9
//接收参数  
int status = 1;
String companyName = "华为";
String brandName = "华为";
//封装对象
Brand brand = new Brand();
brand.setStatus(status);
brand.setBrandName(brandName);
brand.setCompanyName(companyName);

三、Map 对象格式

1
2
3
4
5
6
7
8
9
10
11
12
//接收参数  
int status = 1;
String companyName = "华为";
String brandName = "华为";
//处理参数,用作模糊匹配
companyName = "%" + companyName + "%";
brandName = "%" + brandName + "%";
//map对象
Map map = new HashMap();
map.put("status",status);
map.put("companyName",companyName);
map.put("brandName",brandName);

MaBatis 对动态 SQL 有很强大的支撑

  • if
  • choose(when,otherwise)
  • trim(where,set)
  • foreach

条件查询 - 多个条件中选择一个

多个条件中选择一个,如下图片所示的下拉选项一样
07-下拉菜单(多选一).png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--单条件查询-->
<select id="selectByConditionSingle" resultMap="brandResultMap">
select *
from tb_brand
where
<choose><!--相当于switch-->
<when test="status!=null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName!=null and companyName!=''">
company_name like #{companyName}
</when>
<when test="brandName!=null and brandName!='' ">
and brand_name like #{brandName}
</when>
</choose>
</select>

对于如上代码,当给定一个值时好说,但是如果一个值都没有给的话,此时就需要一个保底的方案,不然 SQL 会报语法错误。

那么我们可以使用 <otherwise> 标签,标签中边放一个恒定值,那么当用户给了一个参数之后,就不会执行 otherwise 中的内容,如果没有给参数的话,恒等式派上用场

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<choose><!--相当于switch-->
<when test="status!=null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName!=null and companyName!=''">
company_name like #{companyName}
</when>
<when test="brandName!=null and brandName!='' ">
and brand_name like #{brandName}
</when>
<otherwise><!--相当于default-->
1 = 1
</otherwise>
</choose>

那么更进一步,可以和前面的 where 标签结合起来,就不需要使用到 otherwise 标签了

1
2
3
4
5
6
7
8
9
10
11
12
13
<where>  
<choose><!--相当于switch-->
<when test="status!=null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName!=null and companyName!=''">
company_name like #{companyName}
</when>
<when test="brandName!=null and brandName!='' ">
and brand_name like #{brandName}
</when>
</choose>
</where>

添加数据

1、编写接口方法:Mapper 接口

  • 参数:除了 id 主键之外的所有参数
  • 结果:void
1
void add(Brand brand);

2、编写 SQL 语句

1
2
3
4
<!--添加功能-->  
<insert id="add">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values(#{brandName},#{companyName},#{ordered},#{description},#{status})</insert>

3、执行方法

1
2
3
4
//4.执行方法  
brandMapper.add(brand);
//提交事务
sqlSession.commit();

在没有手动提交事务以前,虽然没有报错,但是查看数据库,并没有添加数据,原来是 autocommit 设置成了 false,所以需要我们手动提交一下
![[Pasted image 20221203151443.png]]

注:那么每次都需要手动提交事务,那不是挺麻烦。可以在获取 sqlSession 对象的时候就设置好自动提交事务

1
SqlSession sqlSession = sqlSessionFactory.openSession(true);

08-对数据库进行了修改操作后需要手动提交事务.png

MaBatis 事务

  • openSession ():默认开启事务,进行增删改操作后需要使用 sqlSession.commit () 手动提交事务
  • openSession (true):可以设置为自动提交事务(即关闭事务)

添加数据并主键返回

在数据添加成功后,需要获取插入数据库数据的主键的值

通过之前的添加方法,实际上是已经添加了一条数据,但是这个新添加的数据的 id 并没有绑定在这个新添加的对象上,所以 brand.getId() 返回的是一个 null

1
2
3
4
//4.执行方法  
brandMapper.add(brand);
Integer id = brand.getId();
System.out.println(id);

那么如何在添加数据的时候,将主键值和这个新添加的对象绑定在一起呢?

1
2
3
4
<!--添加功能并主键返回-->  
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into tb_brand (brand_name, company_name, ordered, description, status)
values(#{brandName},#{companyName},#{ordered},#{description},#{status})</insert>

在 insert 标签中添加两个属性 useGeneratedKeys="true"keyProperty="id"

修改 - 修改全部字段

1、编写接口方法:Mapper 接口

  • 参数:所有数据
  • 返回类型:void
1
2
3
void update(Brand brand); //不返回结果值

int update(Brand brand); //返回影响的行数

2、编写 SQL 语句

1
2
3
4
5
6
7
8
9
10
<!--修改功能-->
<update id="update">
update tb_brand
set brand_name = #{brandName},
company_name = #{companyName},
ordered = #{ordered},
status = #{status},
description = #{description}
where id = #{id};
</update>

3、执行方法

需要在传入参数的时候把 id 也传进去,然后封装对象的时候,也 brand.setid (id)

1
2
3
//4.执行方法  
int count = brandMapper.update(brand); //返回影响的行数
System.out.println(count);

会存在一个问题,当我们之传入了某几个参数之后,另外几个没有传参,那么运行的话,就会把没有传参的字段赋值为 NULL

修改 - 动态修改字段

比如设置新密码,此时账号就不需要修改,而是只需要修改某几个数据,那么就需要使用到动态 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--修改动态字段-->  
<update id="update2">
update tb_brand
<set>
<if test="brandName!=null and brandName!='' ">
brand_name = #{brandName},
</if>
<if test="companyName!=null and companyName!='' ">
company_name = #{companyName},
</if>
<if test="description!=null and description!='' ">
description = #{description},
</if>
<if test="ordered!=null">
ordered = #{ordered},
</if>
<if test="status!=null">
status = #{status}
</if>
</set>
where id=#{id};
</update>

那么此时,我们可以之修改某几个参数,另外几个参数没有修改的话,也不会被 NULL 值覆盖了

删除一个

1、编写接口方法:Mapper 接口

  • 参数:id
  • 返回类型:void
1
void deleteById(int id);

2、编写 SQL 语句

1
2
3
<delete id="deleteById">  
delete from tb_brand where id = #{id};
</delete>

3、执行方法

传递参数的时候只要将 id 传进来就可以了

1
2
//4.执行方法  
brandMapper.deleteById(id);

批量删除

1、编写接口方法:Mapper 接口

  • 参数:id 数组
  • 返回类型:void
1
void deleteByIds(@Param("ids") int[] ids);

如果没有加注解的话,就报了下面的错误

1
Parameter 'ids' not found. Available parameters are [array, arg0]

2、编写 SQL 语句

占位符的个数需要根据删除的个数进行变化,也就是需要遍历这个数组,MyBatis 中提供了相应的标签 <foreach>,可以用来遍历数组

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--MaBatis会将数组参数封装为一个Map集合,默认情况下
key为array
value为数组
但是通过使用@param注解之后,使用ids已经替换了默认的array,因此collection="ids"
-->
<delete id="deleteByIds">
delete from tb_brand
where id in (
<foreach collection="ids" item="id">
#{id}
</foreach>
)
</delete>

以上 SQL 语句仍然有问题,当有多个 id 时,就变成了如下

1
2
3
<foreach collection="ids" item="id">
#{id} #{id} #{id}
</foreach>

占位符之间少了逗号分隔符,因此还需要在 foreach 标签中添加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--MaBatis会将数组参数封装为一个Map集合,默认情况下
key为array
value为数组
但是通过使用@param注解之后,使用ids已经替换了默认的array,因此collection="ids"
-->
<delete id="deleteByIds">
delete from tb_brand
where id in (
<foreach collection="ids" item="id" separator=",">
#{id}
</foreach>
)
</delete>

3、执行方法

传入一个 ids 数组

1
2
//4.执行方法  
brandMapper.deleteByIds(ids);

改进

如下代码可以正常执行功能,但是 foreach 标签包裹在小括号中,可不可以去掉外边的小括号呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--MaBatis会将数组参数封装为一个Map集合,默认情况下
key为array
value为数组
但是通过使用@param注解之后,使用ids已经替换了默认的array,因此collection="ids"
-->
<delete id="deleteByIds">
delete from tb_brand
where id in (
<foreach collection="ids" item="id" separator=",">
#{id}
</foreach>
)
</delete>

改为如下代码,即在 foreach 标签中添加 open 和 close 属性

1
2
3
4
5
6
7
<delete id="deleteByIds">
delete from tb_brand
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>

MyBatis 参数传递

MyBatis 接口方法中可以接收各种各样的参数,MyBatis 底层对于这些参数进行不同的封装处理方式。扩展阅读:Mybatis @Param 注解的作用_DoUUnderstand 的博客 - CSDN 博客

  • 单个参数
    • POJO 类型:直接使用,属性名和参数占位符名称一致
    • Map 集合:直接使用,键名和参数占位符名称一致
    • Collection:封装为 Map 集合
      • map.put (“arg0”,collection 集合)
      • map.put (“collection”,collection 集合)
    • List:封装成 Map 集合
      • map.put (“arg0”,list 集合)
      • map.put (“collection”,list 集合)
      • map.put (“list”,list 集合)
    • Array:封装成 Map 集合
      • map.put (“arg0”, 数组)
      • map.put (“array”, 数组)
    • 其他类型:直接使用
  • 多个参数
    • 比如查询时的散装参数

总之,当有多个参数时,不要使用 Map 集合中的默认键名,使用 @Param 注解的方式来替换默认的键名

Map 集合中默认的键名如下

1
2
3
4
map.put("arg0",参数值1)
map.put("param1",参数值2)
map.put("arg1",参数值1)
map.put("param2",参数值2)

设置好 @Param 注解之后呢,会将新的键名覆盖掉默认的 arg0、arg1 键名,可读性更强

1
List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String companyName);
1
2
3
4
5
6
7
8
9
10
11
<!--添加@Param前-->
select *
from ...
where status = #{arg0}
and company_name = #{arg1}

<!--添加@Param后-->
select *
from ...
where status = #{status}
and company_name = #{companyName}