·本篇: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 <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 @Repository public interface UmsAdminMapper { 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 @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 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
1 2 3 4 5 6 int updateById (UmsAdmin umsAdmin) ;
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
1 2 3 4 5 6 int deleteById (Long id) ;
1 2 3 <delete id ="deleteById" > delete from ums_admin where id = #{id} </delete >
动态SQL if
if
标签可以实现逻辑判断,以根据用户名和邮箱模糊查询用户
为例
1 2 3 4 5 6 7 List<UmsAdmin> selectByUsernameAndEmailLike (@Param("username") String username, @Param("email") String email) ;
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 <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 <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 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 <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 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 >
1 2 3 4 5 6 List<UmsAdmin> selectByIds (@Param("ids") List<Long> ids) ;
xml
文件中对应的SQL实现,可以使用open
、close
属性指定拼接语句的前后缀
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 @EqualsAndHashCode(callSuper = true) @Data public class UmsResourceExt extends UmsResource { private UmsResourceCategory category; }
1 2 3 4 5 6 7 8 9 10 11 12 13 @Repository public interface UmsResourceMapper { 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 @EqualsAndHashCode(callSuper = true) @Data public class UmsResourceCategoryExt extends UmsResourceCategory { private List<UmsResource> resourceList; }
1 2 3 4 5 6 7 8 9 10 11 12 public interface UmsResourceCategoryMapper { 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 <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 @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); } }
1 2 3 <select id ="selectListByCategoryId" resultMap ="BaseResultMap" > select * from ums_resource where category_id = #{categoryId} </select >