目录
一、简介
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 #打印 sql
map-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 实现如下:
@Test
public 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 实现如下:
@Test
public 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 实现如下:
@Test
public 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 实现如下:
@Test
public 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 语句,十分优雅
@Test
public 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 增加 10
int 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 的变量声明必须是 ew
void updateBatchByIds(
@Param("ew") LambdaQueryWrapper<User> wrapper,
@Param("addAge") int addAge
);
}
如果忘记是 ew 这个东西,也可以通过 Constants 这个常量来找到.
c)在 xml 文件中自定义 sql,并使用 Wrapper 条件
<update id="updateBatchByIds">
update user_info
set 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
@Override
public void xxxMethod(Long id) {
User byId = getById(id);
//...
}
e)当然,这里也提供了对应的 lambda 表达式
@Override
public 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 {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//1.初始化核心插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//2.创建分页插件
PaginationInnerInterceptor page = new PaginationInnerInterceptor(DbType.MYSQL);
//设置分页上线
page.setMaxLimit(100L);
//3.添加分页插件到 MybatisPlus
interceptor.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();