目录
一、简介
1.1、为什么要使用 MybatisPlus
二、使用指南
2.1、依赖
2.2、配置
2.3、常用注解
2.4、BaseMapper 的使用
2.4.1、定义 Mapper 接口
2.4.2、基于 QueryWrapper 的查询(不推荐)
2.4.3、基于 UpdateWrapper 的修改(不推荐)
2.4.4、基于 Lambda 的查询、修改(推荐)
2.5、自定义 SQL
2.6、IService 和 ServiceImpl
2.7、Db 静态工具
2.8、分页插件
2.8.1、配置类
2.8.2、实际使用
一、简介
1.1、为什么要使用 MybatisPlus
a)润物无声:
在 MyBatis 基础上只做增强,不做修改,不会对现有工程产生影响,并且在启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作.
b)效率至上:
MP 提供了很多现成的单表操作,只需简单配置,就可以进行单表的 CRUD,节省大量时间. 另外还提供了 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错.
c)扩展功能:
代码生成、自动分页、逻辑删除、自动填充等等.... 但是我觉得这里有些东西可能自己基建更合适.
二、使用指南
2.1、依赖
MyBatisPlus 的 starter 可以直接替代 MyBatis 的 starter
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency>
2.2、配置
mybatis-plus:type-aliases-package: com/cyk/mp/model/domain # 别名扫描包# 以下配置都是默认值,可以根据项目情况进行修改mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml 文件地址configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印 sqlmap-underscore-to-camel-case: true # 是否开启下划线和驼峰的映射cache-enabled: false # 是否开启二级缓存global-config:db-config:id-type: assign_id # 默认雪花算法,另外还有 auto(自增id) assign_uuid(uuid)update-strategy: not_null # 更新策略: 只更新非空字段
2.3、常用注解
a)常用注解如下:
- @TableName:用来指定表名.
- @TableId:用来指定表中的主键字段信息.
- @TableField:用来指定表中的普通字段信息.
具体的注解参数可以参考官网:注解 | MyBatis-Plus
例如 user_info 表如下:
类定义如下:
@Data
@TableName(value = "user_info", autoResultMap = true)
public class User {@TableId(value = "id", type = IdType.AUTO) //value 默认是 id, type 默认是雪花算法,这里修改为自增长private Long id;@TableField("username")private String name;@TableField("is_married")private Boolean isMarried;private Integer age;//这里会自动驼峰转下划线private Date ctTime;private Date utTime;}
Ps:此案例贯穿全文
b)注意事项如下
@TableField 中若指定 value 生效的前提是: @TableName 中 autoResultMap = true,此设置会按照 TableField 中的设置的 value 自动生成 resultMap.
IdType 枚举类型如下
- AUTO:数据库 ID 自增.
- NONE:无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
- INPUT:通过 set 方法自行注入.
- ASSIGN_ID:分配 ID(主键类型为 Number(Long 和 Integer)或 String),接口 IdentifierGenerator 的方法 nextid 来生成 id,默认是雪花算法.
- ASSIGN_UUID:分配 UUID,主键类型为 String
使用 @TableASSIGN_UUIDField 的常见场景:
- 成员变量与数据库字段名不一致
- 成员变量名以 is 开头,且是布尔值.
- 成员变量名与数据库关键字冲突(反引号解决)
- 成员变量不是数据库字段(@TableField(exist = false) 表示表中不存在该字段,用来排除).
2.4、BaseMapper<T> 的使用
2.4.1、定义 Mapper 接口
自定义的 Mapper 类继承 MyBatisPlus 提供的 BaseMapper 接口:
例如对 User 类操作.
public interface UserMapper extends BaseMapper<User> {}
可以看到 BaseMapper 接口中提供了很多方法,都可以直接调用~
2.4.2、基于 QueryWrapper 的查询(不推荐)
QueryWrapper 通常用来构建 select、delete 的 select 和 where 条件部分,以及 update 的 where 部分.
a)案例一:查询出 name 中带 y 的,age 小于 30 的( id、username、age、is_married )信息.
MP 实现如下:
@Testpublic void test1() {//1.构造查询条件(支持链式编程)QueryWrapper<User> wrapper = new QueryWrapper<User>().select("id", "username", "age", "is_married").like("username", "y").lt("age", 30); // ge大于等于 gt大于 lt小于 le小于等于//2.查询List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);}
MP 实际生成的 SQL 语句如下:
SELECT id,username,age,is_married FROM user_info WHERE (username LIKE ? AND age < ?)
b)案例二:更新 username 为 cyk 的 is_married 字段为 true.
MP 实现如下:
@Testpublic void test2() {//1.要更新的数据User user = new User();user.setIsMarried(true);//2.构造更新条件QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "cyk");//3.执行更新userMapper.update(user, wrapper);}
MP 实际生成的 SQL 语句如下:
UPDATE user_info SET is_married=? WHERE (username = ?)
2.4.3、基于 UpdateWrapper 的修改(不推荐)
UpdateWrapper 通常用来构造 update 语句的 set 和 where 部分.
a)案例一:让 id 为 1,2,3 的用户的 age 加 10.
MP 实现如下:
@Testpublic void test3() {List<Long> ids = List.of(1L,2L,3L);UpdateWrapper<User> wrapper = new UpdateWrapper<User>().setSql("age = age + 10").in("id", ids);userMapper.update(null, wrapper);}
MP 实际生成的 SQL 语句如下:
UPDATE user_info SET age = age + 10 WHERE (id IN (?,?,?))
2.4.4、基于 Lambda 的查询、修改(推荐)
上面提到的这两种方式实际上是硬编码的写法(不推荐),下面讲的 Lambda 写法才是最合适的.
a)案例一:查询出 name 中带 y 的,age 小于 30 的( id、username、age、is_married )信息.
MP 实现如下:
@Testpublic void test4() {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().select(User::getId, User::getName, User::getAge, User::getIsMarried).lt(User::getAge, 30);List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);}
MP 底层执行的 sql 如下:
SELECT id,username,age,is_married FROM user_info WHERE (age < ?)
b)案例二:在公司经常会遇到一个接口复用多个接口的情况,例如根据 ids、name、is_married 查询用户信息,但是这三个参数都有可能为空. 这里 MP 能很好的抛弃 if 语句,十分优雅
@Testpublic void test6() {//1.假设前端传入请求数据UserDTO dto = new UserDTO();dto.setIds(List.of(1L,2L,3L));dto.setName(null);dto.setAgeLeft(10);dto.setAgeRight(30);//2.构造请求LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(!dto.getIds().isEmpty(), User::getId, dto.getIds()).eq(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName()).between(dto.getAgeLeft() != null && dto.getAgeRight() != null,User::getAge, dto.getAgeLeft(), dto.getAgeRight());//3.执行查询List<User> users = userMapper.selectList(wrapper);users.forEach(System.out::println);}
MP 底层执行的 sql 如下:
SELECT id,username,is_married,age,ct_time,ut_time FROM user_info WHERE (id IN (?,?,?) AND age BETWEEN ? AND ?)
2.5、自定义 SQL
MyBatisPlus 更适合于构建复杂的 where 的条件(避免使用一些 动态 sql 显得臃肿),对于例如 update 中出现例如 "set age = age + 10" 这样的语句就只能使用硬编码.
而 MyBatisPlus 是包含 MyBatis 的,因此可以通过 MyBatis 定义 sql 语句中剩下的部分.
总而言之:对于复杂 update 语句,自定义 where 条件之前的语句,MP 构建 where 条件之后的语句
案例:让 id 为 1,2,3 的用户的 age 加 10.
a)基于 Wrapper 构建 where 条件
//让 age 增加 10int addAge = 10;//要修改哪些用户List<Long> ids = List.of(1L, 2L, 3L);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(User::getId, ids);//updateBatchByIds 是自定义的方法userMapper.updateBatchByIds(wrapper, addAge);
b)在 mapper 方法参数中用 Param 注解声明 wrapper 变量名称,必须是 ew
@Mapper
public interface UserMapper extends BaseMapper<User> {//wrapper 的变量声明必须是 ewvoid updateBatchByIds(@Param("ew") LambdaQueryWrapper<User> wrapper,@Param("addAge") int addAge);}
如果忘记是 ew 这个东西,也可以通过 Constants 这个常量来找到.
c)在 xml 文件中自定义 sql,并使用 Wrapper 条件
<update id="updateBatchByIds">update user_infoset age = age + #{addAge} ${ew.customSqlSegment}</update>
MP 实际生成 sql 语句如下:
update user_info set age = age + ? WHERE (id IN (?,?,?))
2.6、IService 和 ServiceImpl
Ps:不太建议在 Service 层乱造,可以自己封装以下... 当然 BaseMapper 也够用.
IService 接口提供中也提供了很多的 方法,并在 ServiceImpl 也给出了具体的实现.
这里的方法和上面讲到的 BaseMapper 大差不差,就不展开讲了.
下面以 User 类为例.
a)首先自定义 mapper 接口,继承 BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {}
a)我们只需要在我们自定义的 IXXXService 上继承 IService
public interface IUserService extends IService<User> {}
b)并在自定义的 XXXServiceImpl 上继承 ServiceImpl 即可
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {}
d)之后这些方法都可以随便用了,例如 getById
@Overridepublic void xxxMethod(Long id) {User byId = getById(id);//...}
e)当然,这里也提供了对应的 lambda 表达式
@Overridepublic List<User> query(QueryUserDTO dto) {return lambdaQuery().in(!dto.getIds().isEmpty(), User::getId, dto.getIds()).eq(StringUtils.hasLength(dto.getName()), User::getName, dto.getName()).between(dto.getMinAge() != null && dto.getMaxAge() != null,User::getAge, dto.getMinAge(), dto.getMaxAge()).list(); //生成一个 list 当然也可以选择生成 page 后面会讲}
模拟创建请求调用 query 方法如下:
QueryUserDTO dto = QueryUserDTO.builder().ids(List.of(1L,2L,3L)).name(null).minAge(10).maxAge(30).build();List<User> users = userService.query(dto);users.forEach(System.out::println);
MP 实际执行的 sql 语句
SELECT id,username,is_married,age,ct_time,ut_time FROM user_info WHERE (id IN (?,?,?) AND age BETWEEN ? AND ?)
2.7、Db 静态工具
可以看到和 IService 中方法几乎一摸一样,只不过 IService 接口里面的方法都是非静态 ,而 DB 这个静态工具里面的方法自然也都是静态的
而且可以看到一些细小的变化,他的方法除了新增和修改以外都需要指定泛型,这是因为他需要通过反射才能拿到实体类的字节码,才知道你的表信息.
那这玩意到底和 IService 有什么区别呢?
假设有两张表,用户表和地址表,并且业务需要根据用户 id 查询地址,又需要根据地址查询用户id,那么就你就可能去 UserService 中注入 AddressService ,而又在 AddressService 中注入 UserService,这样就产生了循环依赖问题.
虽然循环依赖在较新的 Spring 版本中已被解决,但是最好还是需要我们开发人员去注意这个问题,保证系统的稳定性.
此时就可以通过 DB 静态工具类来实现了.
2.8、分页插件
2.8.1、配置类
首先,要在配置类种注册 MyBatisPlus 的核心插件,同时添加分页插件:
@Configuration
public class MybatisConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {//1.初始化核心插件MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//2.创建分页插件PaginationInnerInterceptor page = new PaginationInnerInterceptor(DbType.MYSQL);//设置分页上线page.setMaxLimit(100L);//3.添加分页插件到 MybatisPlusinterceptor.addInnerInterceptor(page);return interceptor;}}
Ps:缺少以上配置,就没法使用分页. 这个插件本质就是在执行指令之前被拦截器拦截下来, 添加分页命令.
2.8.2、实际使用
案例:在公司经常会遇到一个接口复用多个接口的情况,例如根据 ids、name、is_married 查询用户信息,但是这三个参数都有可能为空,最后的结果分页显示(limit 3 offset 0),并根据 id 升序排序.
Ps:在 Page 中若没有使用 addOrder 方法,或者方法参数为空,默认按照创建时间排序.
a)MP 实现如下:
//1.默认请求PageUserDTO dto = PageUserDTO.builder().start(2).limit(1).ids(List.of(1L,2L,3L)).name(null).minAge(10).maxAge(30).build();//2.分页查询//2.1 构建查询LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>().in(!dto.getIds().isEmpty(), User::getId, dto.getIds()).eq(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName()).between(dto.getMinAge() != null && dto.getMaxAge() != null, User::getAge, dto.getMinAge(), dto.getMaxAge());//2.2 构建分页Page<User> page = Page.of(dto.getStart(), dto.getLimit()); //起始页码(非下标), 每页显示条数page.addOrder(new OrderItem("id", true)); //true 表示升序//3.查询Page<User> result = userService.page(page, wrapper);//3.1 分页数据List<User> records = result.getRecords();//3.2 总条数long total = result.getTotal();//3.3 总页数long pages = result.getPages();
MP 底层执行的 sql 如下:
SELECT COUNT(*) AS total FROM user_info WHERE (id IN (?, ?, ?) AND age BETWEEN ? AND ?)SELECT id, username, is_married, age, ct_time, ut_time FROM user_info WHERE (id IN (?, ?, ?) AND age BETWEEN ? AND ?) ORDER BY id ASC LIMIT ?,?
b)但是实际开发一般也不会写的像上述代码一样这么麻烦,更多的可能是这样
//1.默认请求PageUserDTO dto = PageUserDTO.builder().start(2).limit(1).ids(List.of(1L,2L,3L)).name(null).minAge(10).maxAge(30).build();//2.构建分页条件Page<User> page = Page.of(dto.getStart(), dto.getLimit()); //起始页码(非下标), 每页显示条数page.addOrder(new OrderItem("id", true)); //true 表示升序//3.查询Page<User> result = userService.lambdaQuery().in(!dto.getIds().isEmpty(), User::getId, dto.getIds()).eq(StringUtils.isNotBlank(dto.getName()), User::getName, dto.getName()).between(dto.getMinAge() != null && dto.getMaxAge() != null, User::getAge, dto.getMinAge(), dto.getMaxAge()).page(page);//3.1 分页数据List<User> records = result.getRecords();//3.2 总条数long total = result.getTotal();//3.3 总页数long pages = result.getPages();