MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。
官方文档:https://mp.baomidou.com/guide/
SpringBoot整合MP有以下几个步骤:
1、引入Mybatis-Plus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
2、在 yaml 配置文件中配置数据源和要扫描加载的 mappper. xml映射文件
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/spring-boot-example?useUnicode=true&characterEncoding=utf8
username: xxx
password: xxxxxx
hikari:
#最小空闲连接数
minimum-idle: 10
#连接池最大连接数
maximum-pool-size: 20
mybatis-plus:
mapper-locations: classpath:mapper/*Mapper.xml
3、在启动类上加上配置 dao 层接口的扫描
@SpringBootApplication
@MapperScan("com.bin.dao") // 扫描com.bin.dao包下的所有Mapper接口
public class SpringbootFastApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootFastApplication.class, args);
}
}
以下通过几个案例来看看Mybatis-Plus是如何快速、高效的开发
一、使用通用BaseMapper和通用Service CRUD
MP已经对一些常用的CRUD操作做了封装,我们只需要继承它提供的BaseMapper和ServiceImpl即可拥有大多数常见的增删查改功能。
继承 BaseMapper
public interface UserMapper extends BaseMapper<User> {
}
先创建一个UserMapper继承自 BaseMapper
,传入的泛型为User,User实体类如下:
@TableName("sys_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String name;
private Integer age;
// 省略getter和setter
}
@TableName("sys_user")
:含义是User实体类对应的是数据库的 sys_user
表。如果对应的数据库表时user表,那这行注解 @TableName("sys_user")
就可以省略,MP的默认规则会把User实体映射到user表
@TableId(value = "id", type = IdType.AUTO)
:标记此字段对应的是数据库表的主键,主键id的类型为自增长
接着就可以直接在service层使用:
基础的增删查改不用在mapper.xml中写SQL语句了,开发就是这么的快速。
继承 ServiceImpl
@Service
public class UserService extends ServiceImpl<UserMapper, User> implements IUserService {
}
public interface IUserService extends IService<User> {
}
ServiceImpl
对BaseMapper
做了更进一步的封装,我们让自己的UserService
实现类直接继承 ServiceImpl,并在泛型中传入对应的UserMapper
和User实体类- 让
IUserService
接口继承MP提供的IService
接口,并在泛型中传入对应的User实体类
这样就同样可以在service层中拥有丰富的CRUD功能,接着就可以直接在controller层使用:
service层基础的增删查改代码也不用写了,就是这么的快速。
二、分页查询
1、配置分页插件
@SpringBootApplication
@MapperScan("com.bin.dao") // 扫描com.bin.dao包下的所有Mapper接口
public class SpringbootFastApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootFastApplication.class, args);
}
// 分页插件拦截器
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
2、在MP提供的分页查询方法中直接传入MP提供的分页对象
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private IUserService userService;
@RequestMapping("queryPage")
public IPage queryPage() {
// MP的分页对象,这里要查询第2页,页大小是2
Page page = new Page(2, 2);
return userService.page(page);
}
}
执行时生成的SQL语句如下
最终接口返回结果如下
瞬间就完成了分页功能 ^_^
若是需要在XML中自定义分页查询,操作如下
- 对应的XML的SQL和Mapper接口
<?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.bin.dao.UserMapper">
<select id="selectPage" resultType="com.bin.entity.User">
select * from sys_user where age = #{age}
</select>
</mapper>
public interface UserMapper extends BaseMapper<User> {
IPage<User> selectPage(Page<?> page, Integer age);
}
当有传递Page 对象作为参数时,MP会自动加上分页查询, page参数必须放在第一位,否则会报错
大功告成了,生成执行的SQL如下
三、通用枚举处理
在mybatis中默认支持的类型转换器无法处理枚举类型,所以如果想在实体类中以枚举类型作为属性的话,需要让mybatis去支持我们定义的枚举类,让mybatis在传入数据和返回数据的时候帮我们做类型的转换。
1、在配置文件中加入枚举类所在的包扫描
mybatis-plus:
mapper-locations: classpath:dao/*Mapper.xml
type-enums-package: com.bin.constant.enums
这样MP就能帮我们识别到需要转换的枚举类型
2、定义一个枚举类
import com.baomidou.mybatisplus.annotation.EnumValue;
public enum OrderStatusEnum {
WAIT_PAY(1, "待支付"),
PAID(2, "已支付"),
CANCEL(3, "已取消"),
FINISH(4, "已完成");
OrderStatusEnum(int code, String desc){
this.code = code;
this.desc = desc;
}
@EnumValue
private int code;
private String desc;
public int getCode() {
return this.code;
}
public String getDesc() {
return this.desc;
}
@Override
public String toString() {
return this.desc;
}
}
在定义的枚举类中,在code属性上加上 @EnumValue
,表示我们将会以code的值去对应到数据表的字段值
3、在Order类中加入OrderStatusEnum 作为属性
@TableName("t_order")
public class Order implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String orderNo;
private OrderStatusEnum status;
// 省略getter和setter
对应数据库表如下:
说明:实体类中的status字段是枚举类型的,数据库表中的status字段是tinyint类型的
4、验证一下MP的类型转换器效果
@Service
@Transactional
public class OrderService extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Override
public boolean updateOrderStatus(Integer orderId) {
Order order = getBaseMapper().selectById(orderId);
System.out.println(order);
order.setStatus(OrderStatusEnum.CANCEL);
return getBaseMapper().updateById(order) == 1;
}
}
- 执行SQL查询返回结果时,MP自动把数据库返回的 status 字段转换为了 OrderStatusEnum 类对象(
Integer类型
转换为了枚举类型
) - 执行SQL更新时,MP自动把 OrderStatusEnum 类对象转换成了整形值(
枚举类型
转换为了Integer类型
)
四、乐观锁插件
乐观锁
:是对同一资源并发访问控制的一种思想,因为是乐观的,所以它认为每次当前用户读取数据时,别人都不会修改数据,直到最终用户要修改数据时,再判断一下这个数据是否已经被别人修改。
那如何判断数据是否被修改过?
可以在数据库表上增加多一个 version
字段,当某条记录修改成功时,就在这条记录的 version
上 +1,因此是以版本号的方式来判断。
SQL示例:
UPDATE t_order SET order_no=?, version=(读取时的version + 1), status=? WHERE id=? AND version=(读取时的version)
- 如果数据已经被修改,那么
version
就会被+1,在更新时发现版本号已经不匹配,因此数据更新不成功。 - 如果数据没有被修改过,此时
version
和之前读取时的一样,因此操作数据更新就会成功。
具体案例:
小明和小红每天都需要共同处理后台的审核订单,审核通过后系统会发送一条通知给用户。为了避免两个人会同一时刻操作到同一条订单的审核,导致误操作或者脏数据,于是就可以引入在订单记录上加版本号的方案。
在MP中实现乐观锁有如下2个步骤:
1、在启动类配置上OptimisticLockerInterceptor
@SpringBootApplication
@MapperScan("com.bin.dao") // 扫描com.bin.dao包下的所有Mapper接口
public class SpringbootFastApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootFastApplication.class, args);
}
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
2、在User实体类中对应的version字段上加@Version注解
@TableName("t_order")
public class Order implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String orderNo;
private Integer status;
@Version
private Integer version;
// 省略getter和setter
}
具体业务实现如下:
@Service
@Transactional
public class OrderService extends ServiceImpl<OrderMapper, Order> implements IOrderService {
@Override
public boolean updateOrderStatus(Integer orderId, Integer version) {
Order order = getBaseMapper().selectById(orderId);
order.setStatus(OrderStatusEnum.FINISH);
order.setVersion(version);
int i = getBaseMapper().updateById(order);
if (i == 1) {
// 发送消息通知用户
// sendMsg();
return true;
}
return false;
}
}
@RequestMapping("updateOrderStatus/{orderId}/{version}")
public boolean updateOrderStatus(@PathVariable("orderId") Integer orderId, @PathVariable("version") Integer version) {
System.out.println(Thread.currentThread().getName());
return orderService.updateOrderStatus(orderId, version);
}
比如小明和小红两个人同时读取到 orderId=1 的这条订单,当时这条订单的 version 为 8,接着两人同时操作审核通过,前端调用接口时把获取到的版本号传递到接口,当其中一人的请求被处理成功时,会把该订单记录的 version 变成 9,那么另外一个人的操作就不会成功了。
最终执行效果如下:
乐观锁插件会自动把原本的 version = 8 作为过滤条件,并在执行更新时把version更新为 version + 1。
MP在开发中确实是一把利器,帮助我们更快速高效的开发。MP中还有很多丰富的插件扩展,具体查看官方文档:https://mp.baomidou.com/guide/