MyBatis

·本篇:3.5k字 大约需要: 16分钟

MyBatis

MyBatis是一款非常流行的ORM框架,之前一直使用MyBatis-Plus,使用多了之后对MyBatis的一些常规操作不太熟悉,所以在此总结MyBatis的一些常用方法

MyBatis简介

MyBatis是一款优秀的开源持久层框架,支持自定义SQL查询、存储过程和高级映射。在MyBatis中,我们可以在XML中编写SQL语句,然后绑定到Java方法中,通过参数和结果集的自动映射来实现复杂的查询逻辑。MyBatis消除了几乎所有JDBC操作和手动绑定参数操作。

SpringBoot集成MyBatis

  • pom.xml中添加MyBatis提供的Spring Boot Starter
1
2
3
4
5
6
<!--SpringBoot整合MyBatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
  • application.yml中配置好编写SQL实现的xml文件路径,一般存放在resoueces/mapper文件夹下
1
2
3
mybatis:
mapper-locations:
- classpath:mapper/*xml
  • 在代码中进行配置,通过@MapperScan配置Mapper接口路径,就可以开始使用了
1
2
3
@Configuration
@MapperScan("com.t1k.mall.mapper")
public class MyBatisConfig {}

基本使用

MyBatis的基本使用,即基本的增删查改操作

表结构

以mall项目中的ums_admin表为例

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UmsAdmin implements Serializable
{
private Long id;

private String username;

private String password;

@ApiModelProperty(value = "头像")
private String icon;

@ApiModelProperty(value = "邮箱")
private String email;

@ApiModelProperty(value = "昵称")
private String nickName;

@ApiModelProperty(value = "备注信息")
private String note;

@ApiModelProperty(value = "创建时间")
private Date createTime;

@ApiModelProperty(value = "最后登录时间")
private Date loginTime;

@ApiModelProperty(value = "帐号启用状态:0->禁用;1->启用")
private Integer status;
}

select

  • 创建数据操作的接口UmsAdminMapper,并添加根据id查询用户的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 自定义UmsAdmin表查询
*/
@Repository
public interface UmsAdminMapper
{
/**
* 根据id查询用户
* @param id 用户id
* @return 用户对象
*/
UmsAdmin selectByIdSimple(Long id);
}
  • 再创建xml文件UmsAdminMapper.xml,并添加查询方法的SQL实现
1
2
3
4
5
6
7
8
9
<?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.t1k.mall.mapper.UmsAdminMapper">
<select id="selectByIdSimple" resultType="com.t1k.mall.entity.UmsAdmin">
select * from ums_admin where id=#{id}
</select>
</mapper>
  • 编写测试类,注入Mapper,调用Mapper方法进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* UmsAdminMapper基本操作测试方法
*/
@SpringBootTest
public class UmsAdminMapperTest
{
// 打印日志
public static final Logger LOGGER = LoggerFactory.getLogger(UmsAdminMapperTest.class);

@Resource
private UmsAdminMapper umsAdminMapper;

@Test
void selectByIdSimple()
{
UmsAdmin umsAdmin = umsAdminMapper.selectByIdSimple(1L);
LOGGER.info("selectByIdSimple result = {}", umsAdmin);
}
}
  • 对于一些数据库表中以下划线分割的字段无法自动映射,可以通过对字段取别名的方式进行映射,也可以通过在application.yml中开启全局下划线自动转驼峰功能解决
1
2
3
4
mybatis:
configuration:
# 下划线自动转驼峰
map-underscore-to-camel-case: true

insert

  • 编写插入单个用户的方法
1
2
3
4
5
6
/**
* 插入单个用户
* @param umsAdmin 用户对象
* @return 受影响的数据条数
*/
int insert(UmsAdmin umsAdmin);
  • xml文件中编写对应SQL实现,如果需要返回插入后的自增id,需要使用selectKey标签进行配置
1
2
3
4
5
6
7
8
<insert id="insert">
insert into ums_admin(username, password, icon, email, nick_name, note, create_time, login_time)
values (#{username}, #{password}, #{icon}, #{email}, #{nickName}, #{note}, #{createTime}, #{loginTime})
# 插入后返回自增的id
<selectKey keyColumn="id" resultType="long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
  • 编写测试方法进行测试
1
2
3
4
5
6
7
8
9
10
@Test
void insert()
{
UmsAdmin umsAdmin = new UmsAdmin(){{
setUsername("t1k");
setPassword("123");
}};
int result = umsAdminMapper.insert(umsAdmin);
LOGGER.info("insert id = {} result = {}", umsAdmin.getId(), result);
}

update

  • 编写根据id修改用户信息的方法
1
2
3
4
5
6
/**
* 根据id修改用户信息
* @param umsAdmin 用户对象
* @return 受影响的数据条数
*/
int updateById(UmsAdmin umsAdmin);
  • xml文件中编写对应SQL实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<update id="updateById">
update
ums_admin
set
username = #{username},
password = #{password},
icon = #{icon},
email = #{email},
nick_name = #{nickName},
note = #{note},
create_time = #{createTime},
login_time = #{loginTime}
where
id = #{id}
</update>

delete

  • 编写根据id删除用户的方法
1
2
3
4
5
6
/**
* 根据id删除用户
* @param id 用户id
* @return 受影响的数据条数
*/
int deleteById(Long id);
  • xml文件中编写对应SQL实现
1
2
3
<delete id="deleteById">
delete from ums_admin where id = #{id}
</delete>

动态SQL

if

  • if标签可以实现逻辑判断,以根据用户名和邮箱模糊查询用户为例
1
2
3
4
5
6
7
/**
* 根据用户名和邮箱模糊查询用户
* @param username 用户名
* @param email 邮箱
* @return 符合条件的用户
*/
List<UmsAdmin> selectByUsernameAndEmailLike(@Param("username") String username, @Param("email") String email);
  • xml文件中编写对应SQL实现
1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="selectByUsernameAndEmailLike" resultType="com.t1k.mall.entity.UmsAdmin">
# 这里 where 1=1 的作用是防止sql拼接出错
select * from ums_admin where 1=1
<if test="username != null and username != ''">
# concat()函数用于将两个字符串拼接起来,形成一个单一的字符串
# 下面的作用就是 %username%
# 注释中不能再使用引用变量
and username like concat('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
and email like concat('%', #{email}, '%')
</if>
</select>

choose

  • choose标签也可以实现逻辑判断,在if标签的例子中,如果用户名和邮箱为空会查询出所有用户,如果想不查询出所有用户,可以使用choose
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--上面的查询当username和email为空时会查询出所有的用户,
当不想查询出所有用户时,可以使用choose标签,
choose标签能实现这样的功能,我认为是<otherwise>标签那的 and 1=2-->
<select id="selectByUsernameAndEmailLike2" resultType="com.t1k.mall.entity.UmsAdmin">
select * from ums_admin where 1=1
<choose>
<when test="username != null and username != ''">
and username like concat('%', #{username}, '%')
</when>
<when test="email != null and email != ''">
and email like concat('%', #{email}, '%')
</when>
<otherwise>
and 1=2
</otherwise>
</choose>
</select>

where

1
2
3
4
5
6
7
8
9
10
11
12
<!--使用where标签可以避免向前面那样添加where 1=1,当where标签中没有内容时会去除多余的and和where-->
<select id="selectByUsernameAndEmailLike3" resultType="com.t1k.mall.entity.UmsAdmin">
select * from ums_admin
<where>
<if test="username!=null and username!=''">
and username like concat('%',#{username},'%')
</if>
<if test="email!=null and email!=''">
and email like concat('%',#{email},'%')
</if>
</where>
</select>

set

  • 当拼接更新字段的语句时,也会面临如前面一样的问题,所以可以使用set标签来解决,编写根据id选择性修改用户信息的方法
1
2
3
4
5
6
/**
* 根据用户id选择性更新用户信息
* @param umsAdmin 用户对象
* @return 受影响的数据条数
*/
int updateByIdSelective(UmsAdmin umsAdmin);
  • xml中对应的SQL实现,使用set标签,既避免了出现多余的set关键字问题,也会将多余的逗号去除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!--使用<set>标签可以避免多出set字段这样的情况-->
<update id="updateByIdSelective">
update ums_admin
<set>
<if test="username!=null and username!=''">
username = #{username},
</if>
<if test="password!=null and password!=''">
password = #{password},
</if>
<if test="icon!=null and icon!=''">
icon = #{icon},
</if>
<if test="email!=null and email!=''">
email = #{email},
</if>
<if test="nickName!=null and nickName!=''">
nick_name = #{nickName},
</if>
<if test="note!=null and note!=''">
note = #{note},
</if>
<if test="createTime!=null">
create_time = #{createTime},
</if>
<if test="loginTime!=null">
login_time = #{loginTime},
</if>
</set>
where id = #{id}
</update>

foreach

  • 通过foreach可以实现一些循环拼接SQL的逻辑,如编写一个批量插入用户的方法
1
2
3
4
5
6
/**
* 批量插入用户
* @param umsAdminList 要插入的用户列表
* @return 受影响的数据条数
*/
int insertBatch(@Param("entityList") List<UmsAdmin> umsAdminList);
  • xml文件中对应的SQL实现,在foreach标签中的内容会根据传入的集合参数进行循环拼接
1
2
3
4
5
6
7
8
9
<insert id="insertBatch">
insert into
ums_admin(username, password, icon, email, nick_name, note, create_time, login_time)
values
<foreach collection="entityList" separator="," item="item">
(#{item.username}, #{item.password}, #{item.icon}, #{item.email},
#{item.nickName}, #{item.note}, #{item.createTime}, #{item.loginTime})
</foreach>
</insert>
  • 在编写一个根据用户id批量查询的方法
1
2
3
4
5
6
/**
* 根据用户id批量查询
* @param ids 用户id列表
* @return 查询到的用户列表
*/
List<UmsAdmin> selectByIds(@Param("ids") List<Long> ids);
  • xml文件中对应的SQL实现,可以使用openclose属性指定拼接语句的前后缀
1
2
3
4
5
6
<select id="selectByIds" resultType="com.t1k.mall.entity.UmsAdmin">
select * from ums_admin where id in
<foreach collection="ids" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</select>

高级查询

一对一查询

  • 在进行SQL查询时,往往会有一对一的情况,比如mall项目中资源分类ums_resource_category和资源ums_resource两张表,资源和资源分类就是一对一的关系,在进行如此类的查询时,一般会编写一个拓展类来继承实体类,实体类放在entity包下,拓展类放在domain包下,如下
1
2
3
4
5
6
7
8
9
/**
* UmsResource拓展类
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UmsResourceExt extends UmsResource
{
private UmsResourceCategory category;
}
  • 编写一个根据资源id获取资源及资源分类信息的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 自定义UmsResource表查询
*/
@Repository
public interface UmsResourceMapper
{
/**
* 根据资源表id获取资源及资源分类信息
* @param id 资源表id
* @return 资源及分类信息
*/
UmsResourceExt selectResourceWithCategory(Long id);
}
  • xml文件中对应SQL实现,可以通过给ums_resource_category表中字段取以category.xxx的别名来进行自动映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<select id="selectResourceWithCategory" resultType="com.t1k.mall.domain.UmsResourceExt">
select
ur.id,
ur.create_time as createTime,
ur.name,
ur.url,
ur.description,
ur.category_id as categoryId,
urc.id as "category.id",
urc.name as "category.name",
urc.sort as "category.sort",
urc.create_time as "category.createTime"
from
ums_resource ur
left join
ums_resource_category urc on ur.category_id = urc.id
where
ur.id = #{id}
</select>
  • 还可以通过ResultMap+association标签来实现,当编写xml文件之前,一般会先给当前文件编写一个BaseResultMap,用于对当前表的字段和对象属性进行直接映射,例如在UmsResourceMapper.xml中这样实现
1
2
3
4
5
6
7
8
<resultMap id="BaseResultMap" type="com.t1k.mall.entity.UmsResource">
<id column="id" property="id"/>
<result column="create_time" property="createTime"/>
<result column="name" property="name"/>
<result column="url" property="url"/>
<result column="description" property="description"/>
<result column="category_id" property="categoryId"/>
</resultMap>
  • UmsResourceCategoryMapper.xml中这样实现
1
2
3
4
5
6
<resultMap id="BaseResultMap" type="com.t1k.mall.entity.UmsResourceCategory">
<id column="id" property="id"/>
<result column="create_time" property="createTime"/>
<result column="name" property="name"/>
<result column="sort" property="sort"/>
</resultMap>
  • 然后可以实现一对一ResultMap,可以使用association标签进行一对一管理,配置columnPrefix属性将匹配到的字段直接映射到关联对象中去
1
2
3
<resultMap id="ResourceWithCategoryMap" type="com.t1k.mall.domain.UmsResourceExt" extends="BaseResultMap">
<association property="category" columnPrefix="category_" resultMap="com.t1k.mall.mapper.UmsResourceCategoryMapper.BaseResultMap"/>
</resultMap>
  • 然后再编写xml文件中的对应SQL实现,直接使用上面的ResultMap,同时给ums_resource_category表中的字段指定了category_前缀以便于映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<select id="selectResourceWithCategory" resultMap="ResourceWithCategoryMap">
select
ur.id,
ur.create_time,
ur.name,
ur.url,
ur.description,
ur.category_id,
urc.id as category_id,
urc.name as category_name,
urc.sort as category_sort,
urc.create_time as category_create_time
from
ums_resource ur
left join
ums_resource_category urc
on
ur.category_id = urc.id
where
ur.id = #{id}
</select>

一对多映射

  • 如资源分类和资源就是一种一对多的情况
1
2
3
4
5
6
7
8
9
/**
* UmsResourceCategory拓展类
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UmsResourceCategoryExt extends UmsResourceCategory
{
private List<UmsResource> resourceList;
}
  • 编写一个根据分类id获取资源分类及对应资源的方法
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 自定义UmsResourceCategory表查询
*/
public interface UmsResourceCategoryMapper
{
/**
* 根据分类id获取资源分类及对应资源
* @param id 分类表id
* @return 分类及对应资源
*/
UmsResourceCategory selectCategoryWithResource(Long id);
}
  • xml文件中配置ResultMap,并通过collection标签建立一对多关系
1
2
3
<resultMap id="selectCategoryWithResourceMap" type="com.t1k.mall.domain.UmsResourceCategoryExt" extends="BaseResultMap">
<collection property="resourceList" columnPrefix="resource_" resultMap="com.t1k.mall.mapper.UmsResourceMapper.BaseResultMap"/>
</resultMap>
  • xml文件中实现对应SQL,并使用上面的ResultMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<select id="selectCategoryWithResource" resultMap="selectCategoryWithResourceMap">
select
urc.id,
urc.create_time,
urc.name,
urc.sort,
ur.id resource_id,
ur.create_time resource_create_time,
ur.name resource_name,
ur.url resource_url,
ur.description resource_description,
ur.category_id resource_category_id
from
ums_resource_category urc
left join
ums_resource ur
on
urc.id = ur.category_id
where
urc.id = #{id}
</select>

分页插件

  • 在实现查询逻辑时,往往会遇到分页查询的请求,这里使用开源的PageHelper插件,首先在pom.xml中添加它的starter
1
2
3
4
5
6
<!--MyBatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
  • 然后在查询方法之前使用它的startPage方法传入分页参数即可,分页后得到的数据可以在PageInfo中获取到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* UmsResource的Service接口实现类
*/
@Service
public class UmsResourceServiceImpl implements UmsResourceService
{

@Resource
private UmsResourceMapper umsResourceMapper;

@Override
public PageInfo<UmsResource> page(Integer pageNum, Integer pageSize, Long categoryId) {
PageHelper.startPage(pageNum, pageSize);
List<UmsResource> resourceList = umsResourceMapper.selectListByCategoryId(categoryId);
return new PageInfo<>(resourceList);
}
}
  • xml中SQL实现
1
2
3
<select id="selectListByCategoryId" resultMap="BaseResultMap">
select * from ums_resource where category_id = #{categoryId}
</select>