MyBatis-Plus

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 简称MP,是在Mybatis的基础上进行增强,用户简化开发,提高效率。只做增强不做改变支持主键自动生成、内置代码生成器、内置分页插件

一、简述

简称MP,是在Mybatis的基础上进行增强,用户简化开发,提高效率。只做增强不做改变

支持主键自动生成、内置代码生成器、内置分页插件

官网:MyBatis-Plus

二、快速开始

2.1 设计表

1. SET NAMES utf8mb4;
2. SET FOREIGN_KEY_CHECKS = 0;
3. 
4. -- ----------------------------
5. -- Table structure for t_user
6. -- ----------------------------
7. DROP TABLE IF EXISTS `t_user`;
8. CREATE TABLE `t_user`  (
9.   `id` int(11) NOT NULL AUTO_INCREMENT,
10.   `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
11.   `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
12.   `nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
13.   `age` int(11) NULL DEFAULT NULL,
14.   `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
15.  PRIMARY KEY (`id`) USING BTREE
16. ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
17. 
18. -- ----------------------------
19. -- Records of t_user
20. -- ----------------------------
21. INSERT INTO `t_user` VALUES (1, 'zs', '1234', '张三', 14, '北京');
22. INSERT INTO `t_user` VALUES (2, 'ls', '1234', '李四', 15, '河南');
23. 
24. SET FOREIGN_KEY_CHECKS = 1;
25.

2.2 添加依赖

1. <dependency>
2.    <groupId>com.baomidou</groupId>
3.    <artifactId>mybatis-plus-boot-starter</artifactId>
4.    <version>3.5.1</version>
5. </dependency>

2.3 配置数据源

1. spring:
2.   datasource:
3.     driver-class-name: com.mysql.jdbc.Driver
4.     url: jdbc:mysql://localhost:3306/xxx
5.     username: root
6.     password: 1234

2.4 创建POJO

1. @Data
2. @AllArgsConstructor
3. @NoArgsConstructor
4. @TableName("t_user")
5. public class User {
6.    private Integer id;
7.    private String userName;
8.    private String password;
9.    private String nickName;
10.    private Integer age;
11.    private String address;
12. }

2.5 创建Mapper接口

创建的Mapper接口去继承BaseMapper,泛型为POJO的类型

1. public interface UserMapper extends BaseMapper<User> {
2. }

2.6 创建启动类

在启动类上添加@MapperScan注解,扫描Mapper接口所在的包

1. @SpringBootApplication
2. @MapperScan("com.jiuxiao.mapper")
3. public class MPApp {
4.    public static void main(String[] args) {
5.         SpringApplication.run(MPApp.class,args);
6.     }
7. }

2.7 编写测试方法

1. @SpringBootTest
2. public class MPTest {
3. 
4.    @Autowired
5.     UserMapper userMapper;
6. 
7.    @Test
8.    void testSelectAll() {
9.         List<User> users = userMapper.selectList(null);
10.         System.out.println(users);
11.     }
12. }

三、常用设置

3.1 日志打印

在控制台中打印操作的SQL语句

1. mybatis-plus:
2.   configuration:
3.     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

3.2 设置表映射规则

默认情况下MP在操作表名时,操作的是类名,如果类名和表名不一致就需要进行手动设置映射规则

① 单个设置

在实体类上添加@TableName注解,声明数据库表名

1. @TableName("t_user")
2. public class User {
3. // ...
4. }

② 全局设置

如果每个表的前缀都是t_,需要在每一个实体类上添加注解,比较频繁。可以使用以下配置

1. mybatis-plus:
2.   global-config:
3.     db-config:
4.       table-prefix: t_

3.3 开启字段和属性的驼峰映射

从经典数据库列名A_COLUMN映射到经典Java属性名aColumn。

举例:

  • 字段名为user_name,自动映射成userName

使用前提:

  • SQL中的字段名:全部小写,每个单词之间使用下划线分割
  • java中的属性名:首字母小写,其余单词首字母大写

该配置默认是开启的,无需任何配置

1. mybatis-plus:
2.   configuration:
3.     map-underscore-to-camel-case: true

3.4 字段和属性映射

MP中默认操作的字段名为实体类中的属性名,如果字段名和属性名不一致,需要手动映射

在实体类的属性上添加@TableField注解,声明表中字段名

1. public class User {
2. @TableField("user_name")
3. private String userName;
4. 
5. @TableField("nick_name")
6. private String nickName;
7. }

3.5 主键生成策略

  • AUTO

使用数据库中的自增策略,前提是表中主键声明了自增

  • NONE

默认策略(没有设置主键类型),使用雪花算法生成一个10位的数字

  • INPUT

手动给主键赋值,否则表中字段值为null

  • ASSING_ID

当实体类中的主键属性值为null时,使用雪花算法分配ID【当前主键类型必须是Long和Integer和String】

  • ASSING_UUID

当实体类中的主键属性值为null时,使用UUID分配ID【当前主键类型必须是String】

① 单个设置

在属性上添加@TableId注解,声明是一个主键

  • value:对应的是表中的主键字段名,如果字段名为id,该属性可以省略
  • type:是一个枚举类型,声明主键生成策略
1. public class User {
2. @TableId(value = "id",type = IdType.xxx)
3. private Integer id;
4. }

② 全局设置

如果每个表中的主键生成策略一致,可以使用全局配置

1. mybatis-plus:
2.   global-config:
3.     db-config:
4.       id-type: auto

四、CRUD

4.1 增加

  • 传入实体类对象,增加之后通过getter方法可以获取主键值
1. @Test
2. void testInsert() {
3. User user = new User(null, "ww", "4444", "王五", 16, "河北");
4.     userMapper.insert(user);
5.     System.out.println("增加之后的主键 --> "+user.getId());
6. }

4.2 查询

  • 根据id查询
1. @Test
2. void testSelectById() {
3. User user = userMapper.selectById(1);
4.     System.out.println(user);
5. }
  • 多条件查询
1. @Test
2. void testSelectByMap() {
3.     Map<String,Object> map = new HashMap<>();
4.     map.put("age",14);
5.     map.put("address","北京");
6.     List<User> list = userMapper.selectByMap(map);
7.     System.out.println(list);
8. }
  • 批量查询
1. @Test
2. void testSelectBatch() {
3.     List<Integer> ids = Stream.of(2, 3).collect(Collectors.toList());
4.     List<User> users = userMapper.selectBatchIds(ids);
5.     System.out.println(users);
6. }

4.3 更新

  • 只更属性值不为null的字段,如果类型为基本数据类型,相对应的字段将会被修改成默认值

比如int默认值为0,对应的字段值将会被修改成0

1. @Test
2. void testUpdate() {
3. User user = new User();
4.     user.setId(1);
5.     user.setAddress("河北");
6.     user.setAge(66);
7.     userMapper.updateById(user);
8. }

4.4 删除

  • 根据id删除
1. @Test
2. void testDeleteById() {
3.     userMapper.deleteById(3);
4. }
  • 多条件删除

也可以传入对象

1. @Test
2. void testDeleteByMap() {
3.     Map<String,Object> map = new HashMap<>();
4.     map.put("nick_name","李四");
5.     map.put("user_name","ls");
6.     userMapper.deleteByMap(map);
7. }
  • 批量删除
1. @Test
2. void testDeleteBatch() {
3.     List<Integer> ids = Stream.of(1, 2).collect(Collectors.toList());
4.     userMapper.deleteBatchIds(ids);
5. }

五、条件构造器Wrapper

5.1 概述

在操作数据库中会牵扯到很多条件,所以MP提供了一个强大的条件构造器Wrapper,使用它可以让我们非常方便的构造条件

继承体系:

在其子类AbstractWrapper中提供了很多构建Where条件的方法

AbstractWrapper的子类QueryWrapper中提供了用于针对SELECT语句的select方法,可以设置只查询哪些字段

AbstractWrapper的子类UpdateWrapper中提供了用户针对SET语句的set方法,可以设置只更新哪些字段

5.2 AbstractWrapper中常用方法

eq

  • 等于 =
  • 例:eq(name,"张三") --> name = '张三'

allEq

1. allEq(Map<R, V> params)
2. allEq(Map<R, V> params, boolean null2IsNull)
3. allEq(boolean condition,BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

参数说明

  • params:key为数据库中的字段名,value是条件
  • null2IsNull:为true时,map集合中valuenull的调用isNull方法;为false时则忽略valuenull
  • filter:过滤函数,是否允许字段传入比对条件中

  • allEq({id : 1,name : "张三",age : null}) --> id = 1 and name = '张三' and age is null
  • allEq({id : 1,name : "张三",age : null},false) --> id = 1 and name = '张三'
  • allEq((k,v)->k.indexOf('a') >= 0,{id : 1,name : "张三",age : null},false) --> name = '张三'

ne

  • 不等于 <>
  • 例:ne("name","李四") --> name <> '李四'

gt

  • 大于 >
  • 例:gt("age",18) --> age > 18

ge

  • 大于等于 >=
  • 例:ge("age",18) --> age >= 18

lt

  • 小于 <
  • 例:lt("age",18) --> age < 18

le

  • 小于等于 <=
  • 例:le("age",18) --> age <= 18

between

  • BETWEEN 值1 AND 值2
  • 例:between("age",15,40) --> age BETWEEN 15 AND 40

like

  • LIKE %值%
  • 例:like("name","王") --> name like '%王%'

likeLeft

  • LIKE %值
  • 例:likeLeft("name","三") --> name like '%三'

likeRight

  • LIKE 值%
  • 例:likeRight("name","李") --> name like '李%'

isNull

  • 字段 IS NULL
  • 例:inNull("address") --> address is null

isNotNull

  • 字段 IS NOT NULL
  • 例:isNotNull("id") --> id is not null

in

  • 字段 IN (v0,v1)
  • 例:in("id",1,2,3) --> id in (1, 2, 3)

① 示例

需求:查询address以"河"开头、成年的用户信息

SQL语句

1. select
2.  *
3. from
4.  t_user
5. where
6.  address like '河%'
7. and
8.  age >= 18

使用Wrapper查询

1. @Test
2. void testWrapper() {
3.     QueryWrapper<User> wrapper = new QueryWrapper<>();
4.     wrapper.likeRight("address","河");
5.     wrapper.ge("age",18);
6.     List<User> users = userMapper.selectList(wrapper);
7.     System.out.println(users);
8. }

② 示例

需求:查询id为1,2,3、age在20和25之间、password为1234的用户信息

SQL语句

1. select
2.  *
3. from
4.  t_user
5. where
6.  id in (1,2,3)
7. and
8.  age between 20 and 25
9. and
10.   `password` = '1234'

使用Wrapper查询

1. @Test
2. void testWrapper() {
3.     QueryWrapper<User> wrapper = new QueryWrapper<>();
4.     wrapper.in("id",1,2,3);
5.     wrapper.between("age",20,25);
6.     wrapper.eq("password","1234");
7.     List<User> users = userMapper.selectList(wrapper);
8.     System.out.println(users);
9. }

③ 示例

需求:查询address中含有北,nick_name为不为空的用户信息,并且根据id倒序

SQL语句

1. select
2.  *
3. from
4.  t_user
5. where
6.  `address` like '%北%'
7. and
8.  nick_name is not null
9. order by
10.  id desc

使用Wrapper查询

1. @Test
2. void testWrapper() {
3.     QueryWrapper<User> wrapper = new QueryWrapper<>();
4.     wrapper.like("address","北");
5.     wrapper.isNotNull("nick_name");
6.     wrapper.orderByDesc("id");
7.     List<User> users = userMapper.selectList(wrapper);
8.     System.out.println(users);
9. }

5.3 QueryWrapper

使用select方法可以设置要查询的字段

① 方法一

select(String... sqlSelect);

参数

  • 查询的字段
1. @Test
2. void testQueryWrapper() {
3.     QueryWrapper<User> wrapper = new QueryWrapper<>();
4.     wrapper.select("id","nick_name","address");
5.     List<User> users = userMapper.selectList(wrapper);
6.     System.out.println(users);
7. }

② 方法二

select(Predicate<TableFieldInfo> predicate);

参数

  • 需要过滤的字段【无法过滤id】
1. @Test
2. void testQueryWrapper() {
3.     QueryWrapper<User> wrapper = new QueryWrapper<>(new User());
4.     wrapper.select(tableFieldInfo -> "nick_name".equals(tableFieldInfo.getColumn()) || "address".equals(tableFieldInfo.getColumn()));
5.     List<User> users = userMapper.selectList(wrapper);
6.     System.out.println(users);
7. }

③ 方法三

select(Class<T> entityClass, Predicate<TableFieldInfo> predicate);

参数

  • 实体类字节码
  • 需要过滤的字段【无法过滤id】
1. @Test
2. void testQueryWrapper() {
3.     QueryWrapper<User> wrapper = new QueryWrapper<>();
4.     wrapper.select(User.class,tableFieldInfo -> !"user_name".equals(tableFieldInfo.getColumn()));
5.     List<User> users = userMapper.selectList(wrapper);
6.     System.out.println(users);
7. }

5.4 UpdateWrapper

使用set方法设置只修改哪些字段,在使用的过程中还能够去构建复杂的WHERE条件

1. @Test
2. void testUpdateWrapper() {
3.     UpdateWrapper<User> wrapper = new UpdateWrapper<>();
4.     wrapper.gt("age",18);
5.     wrapper.set("password","6666");
6.     userMapper.update(new User(),wrapper);
7. }

5.5 LambdaWrapper

之前在构造条件时字段名都是以字符串的形式,这种无法在预编译期确定字段名的合法性

MP提供了Lambda条件构造器,直接以实体类的方法引用形式来指定字段名

1. @Test
2. void testLambdaWrapper() {
3.     LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
4.     wrapper.likeRight(User::getAddress,"河");
5.     wrapper.ge(User::getAge,"18");
6.     wrapper.select(User::getUserName,User::getAddress);
7.     List<User> users = userMapper.selectList(wrapper);
8.     System.out.println(users);
9. }

六、Mapper层自定义方法

6.1 概述

虽然MP提供了很多常用的方法,但遇到一些复杂的语句时,就需要手动编写SQL语句

因为MP是在MyBatis的基础上增强,所以还是支持之前MyBatis的方式自定义方法

同时还支持在自定义方法中能够继续使用条件构造器Wrapper

6.2 Mybatis方式

① 定义接口方法

1. public interface UserMapper extends BaseMapper<User> {
2. 
3.    User selectById(Integer id);
4. }

② 定义xml文件

1. <?xml version="1.0" encoding="UTF-8" ?>
2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
3. <mapper namespace="com.jiuxiao.mapper.UserMapper">
4. 
5. </mapper>

③ 编写SQL语句

1. <sql id="Bean_column">
2.     id,user_name,password,nick_name,age,address
3. </sql>
4. <select id="selectById" resultType="com.jiuxiao.pojo.User">
5.     select
6. <include refid="Bean_column"/>
7.     from
8.         t_user
9.     where
10.         id = #{id}
11. </select>

6.3 使用条件构造器

在使用自定义方法时,如果也想像MP中自定的方法一样使用条件构造器,只需要以下配置:

  1. 方法定义中将Wrapper以参数传入,并指定其参数名
1. public interface UserMapper extends BaseMapper<User> {
2. 
3.    List<User> selectByWrapper(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
4. }
  1. 在SQL语句中将Wrapper中的SQL片段进行拼接

注意:不能使用#{},必须使用${},因为该SQL片段中含有关键字,需要经过预编译,不能以占位符的形式传递

1. <select id="selectByWrapper" resultType="com.jiuxiao.pojo.User">
2.     select
3. <include refid="Bean_column"/>
4.     from
5.         t_user
6.     ${ew.customSqlSegment}
7. </select>

七、分页查询

7.1 单表分页查询

  1. 配置分页查询拦截器
1. @Configuration
2. public class MybatisPlusConfig {
3.  @Bean
4. public MybatisPlusInterceptor MybatisPlusInterceptor() {
5. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
6.         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
7. return interceptor;
8.     }
9. }
  1. 测试分页查询
1. @Test
2. void testPage() {
3.     IPage<User> page = new Page<>();
4. // 设置每页条数
5.     page.setSize(3);
6. // 设置查询页数
7.     page.setCurrent(1);
8.     page = userMapper.selectPage(page,null);
9.     System.out.println(page.getRecords()); // 获取每页数据
10.     System.out.println(page.getTotal()); // 获取总条数
11. }

7.2 多表分页查询

如果在进行多表查询时,并使用到分页,就可以在Mapper接口中自定义方法,然后让方法接收Page对象

需求:查询所有的用户信息以及查询所属订单

  1. 表设计
1. SET NAMES utf8mb4;
2. SET FOREIGN_KEY_CHECKS = 0;
3. 
4. -- ----------------------------
5. -- Table structure for t_order
6. -- ----------------------------
7. DROP TABLE IF EXISTS `t_order`;
8. CREATE TABLE `t_order`  (
9.   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
10.   `price` int(11) NULL DEFAULT NULL COMMENT '价格',
11.   `user_id` int(11) NULL DEFAULT NULL COMMENT '创建人id',
12.   `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
13.   `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
14.   `version` int(11) NULL DEFAULT 1 COMMENT '版本号',
15.   `del_flag` int(1) NULL DEFAULT 1 COMMENT '1-已删除 0-未删除',
16. PRIMARY KEY (`id`) USING BTREE
17. ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
18. 
19. -- ----------------------------
20. -- Records of t_order
21. -- ----------------------------
22. INSERT INTO `t_order` VALUES (1, 200, 1, '2022-10-19 21:28:52', '2022-10-22 21:28:54', 1, 0);
23. INSERT INTO `t_order` VALUES (2, 100, 1, '2022-10-08 21:29:11', '2022-10-18 21:29:15', 1, 0);
24. INSERT INTO `t_order` VALUES (3, 15, 2, '2022-09-29 21:29:39', '2022-10-15 21:29:43', 1, 0);
25. 
26. SET FOREIGN_KEY_CHECKS = 1;
  1. 定义实体类
1. @Data
2. @AllArgsConstructor
3. @NoArgsConstructor
4. public class Order {
5. private Long id;
6. private Integer price;
7. private Integer userId;
8. private Date createTime;
9. private Date updateTime;
10. private Integer version;
11. private Integer delFlag;
12. }
  1. 定义Mapper接口

只需要定义,无需处理,拦截器会自动处理

1. public interface UserMapper extends BaseMapper<User> {
2. 
3.    IPage<User> selectByPage(Page<User> page);
4. }
  1. 编写SQL语句:
1. <resultMap id="Bean_result" type="com.jiuxiao.pojo.User">
2. <id property="id" column="id"/>
3. <result property="userName" column="user_name"/>
4. <result property="password" column="password"/>
5. <result property="nickName" column="nick_name"/>
6. <result property="age" column="age"/>
7. <result property="address" column="address"/>
8. <collection property="list"
9. select="com.jiuxiao.mapper.OrderMapper.selectByUserId"
10. fetchType="lazy"
11. column="id"/>
12. </resultMap>
13. <select id="selectByPage" resultMap="Bean_result">
14.     select
15.       id,user_name
16.     from
17.       t_user
18. </select>
  1. 分步查询OrderMapper接口
1. public interface OrderMapper{
2. 
3. @Select("select * from t_order where user_id = #{id}")
4.     List<Order> selectByUserId(Integer id);
5. }
  1. 测试
1. @Test
2. void testPageMethod() {
3.     IPage<User> page = new Page<>();
4.     page.setSize(2);
5.     page.setCurrent(1);
6.     page = userMapper.selectByPage(page);
7.     System.out.println(page.getRecords());
8.     System.out.println(page.getTotal());
9. }

八、Service接口

8.1 概述

在实际业务中肯定需要定义Service接口,MP中提供了Service层的实现。接口继承IService,其实现类继承ServiceImpl,即可使用

相对于Mapper接口,Service层提供了更多批量操作的方法

8.2 使用

  1. 接口
1. public interface UserService extends IService<User> {
2. 
3. }
  1. 实现类
1. @Service
2. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
3. 
4. }
  1. 测试
1. @Autowired
2. UserService userService;
3. 
4. @Test
5. void testServiceMethod() {
6. User user = userService.getById(3);
7.     System.out.println(user);
8. }

其他方法查看官方文档

8.3 自定义Service层方法

  1. 接口
1. public interface UserService extends IService<User> {
2. 
3. User selectByIdOfOrder(Integer id);
4. }
  1. 实现类
1. @Service
2. public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
3. 
4. @Autowired
5.     OrderMapper orderMapper;
6. 
7. @Override
8. public User selectByIdOfOrder(Integer id) {
9. // 获取mapper
10. UserMapper userMapper = baseMapper;
11. User user = userMapper.selectById(1);
12.         List<Order> orders = orderMapper.selectByUserId(user.getId());
13.         user.setList(orders);
14. return user;
15.     }
16. }
  1. 测试
1. @Autowired
2. UserService userService;
3. 
4. @Test
5. void testCustomService() {
6. User user = userService.selectByIdOfOrder(1);
7.     System.out.println(user);
8. }

九、自动填充

9.1 概述

在实际业务中的表都会有更新时间、创建时间等字段

可以使用@TableField注解中的fill属性来设置字段的自动填充

9.2 使用

① 添加注解

使用@TableField注解中的fill属性来标注哪些字段需要自动填充,加了注解MP才会在SQL语句中为我们预留字段。

该属性的属性值就代表进行什么操作时进行自动填充,该属性值有:

  • INSERT:进行增加操作时自动填充
  • UPDATE:进行更新操作时自动填充
  • INSERT_UPDATE:进行增加和更新时自动填充
  • DEFAULT:默认不处理
1. public class Order {
2. // ...
3. @TableField(fill = FieldFill.INSERT)
4. private Date createTime;
5. @TableField(fill = FieldFill.UPDATE)
6. private Date updateTime;
7. }

② 自定义填充处理器 MyMetaObjectHandler

1. @Component
2. public class MyMetaObjectHandler implements MetaObjectHandler {
3. 
4. @Override
5. public void insertFill(MetaObject metaObject) {
6. // this.setFieldValByName("createTime",LocalDateTime.now(),metaObject);
7. this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
8.     }
9. 
10. @Override
11. public void updateFill(MetaObject metaObject) {
12. // this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
13. this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
14.     }
15. }

注意:

  • 未注释的地方,时间类型只能是LocalDateTime
  • 注释的地方,可以传入任意类型。比如:Date,LocalDateTime
  • 如果参数中的类型和实体类中的类型不匹配,传入的值为null
  • 自动填充的值为LocalDateTime.now(),那么对应的属性类型必须为LocalDateTime
  • 自动填充的值为new Date(),那么对应的属性类型必须为Date
  • 如果mysql-connector-java版本低于5.1.37,使用Java8的日期类型会抛出异常
  • 异常信息:Data truncation: Incorrect datetime value: '\xAC\xED\x00\x05sr\x00\x0Djava.time.Ser\x95]\x84\xBA\x1B"H\xB2\x0C\x00\x00xpw\x0E\x05\x00\x00\x07\xE6\x0A\x14\x0C\x0B2$\xA8'\xC0' for column 'update_time' at row 1

十、逻辑删除

MP也支持逻辑删除的处理,只需要配置好逻辑删除的的实体类属性名,再指定删除的值和未删除的值即可

注意:如果是3.3.0之前的版本,属性类中需要加入@TableLogic注解

1. mybatis-plus:
2.   global-config:
3.     db-config:
4.       logic-delete-field: delFlag # 实体类属性名
5.       logic-delete-value: 1 # 逻辑已删除值(默认是1)
6.       logic-not-delete-value: 0 # 逻辑未删除值(默认是0)
  • 删除

将逻辑删除的字段修改成

UPDATE t_order SET del_flag=1 WHERE id=? AND del_flag=0
  • 查询
SELECT id,price,user_id,create_time,update_time,version,del_flag FROM t_order WHERE del_flag=0

十一、乐观锁

11.1 概述

在并发操作时,我们需要保证对数据进行操作时不会发生冲突。乐观锁就是一种解决方案,通过版本号防止操作数据发生冲突问题

使用乐观锁,一般会在表中加入一个version字段,每次对某个列进行操作,对应的version版本+1

在进行更新时操作时,先去查询对应数据的version值,然后在执行修改的时候:set version = 老版本 + 1 where version = 老版本

如果在此期间有人修改了这个版本号,那么这次的修改将会失败

手动实现乐观锁有点麻烦,所以MP为我们提供了插件进行使用

11.2 演示冲突

两个线程同时去更新同一条数据

1. @Test
2. void testVersion() {
3. new Thread(() -> {
4. Order order = new Order();
5.         order.setId(6L);
6.         order.setPrice(55);
7. int count = orderMapper.updateById(order);
8.         System.out.println("分支线程 -->"+count);
9.     }).start();
10. Order order = new Order();
11.     order.setId(6L);
12.     order.setPrice(66);
13. int count = orderMapper.updateById(order);
14.     System.out.println("主线程 -->"+count);
15. }

都完成了修改:最后一次修改后的数据覆盖第一次修改后的数据

1. ==>  Preparing: UPDATE t_order SET price=?,WHERE id=?
2. ==>  Preparing: UPDATE t_order SET price=?, update_time=? WHERE id=?
3. ==> Parameters: 55(Integer), 6(Long)
4. ==> Parameters: 66(Integer), 6(Long)
5. <==    Updates: 1
6. <==    Updates: 1
7. 分支线程 -->1
8. 主线程 -->1

11.3 使用乐观锁解决

① 手动解决

1. @Test
2. void testVersion() {
3. new Thread(() -> {
4. Order order = orderMapper.selectById(6L);
5.         UpdateWrapper<Order> wrapper = new UpdateWrapper<>();
6.         wrapper.set("price",333);
7.         wrapper.set("version",order.getVersion()+1);
8.         wrapper.eq("id",order.getId());
9.         wrapper.eq("version",order.getVersion());
10. int count = orderMapper.update(new Order(), wrapper);
11.         System.out.println("分支线程 -->"+count);
12.     }).start();
13. Order order = orderMapper.selectById(6L);
14.     UpdateWrapper<Order> wrapper = new UpdateWrapper<>();
15.     wrapper.set("price",222);
16.     wrapper.set("version",order.getVersion()+1);
17.     wrapper.eq("id",order.getId());
18.     wrapper.eq("version",order.getVersion());
19. int count = orderMapper.update(new Order(), wrapper);
20.     System.out.println("主线程 -->"+count);
21. }
1. ==>  Preparing: UPDATE t_order SET price=?,version=? WHERE (id = ? AND version = ?)
2. ==>  Preparing: UPDATE t_order SET price=?,version=? WHERE (id = ? AND version = ?)
3. ==> Parameters: 222(Integer), 2(Integer), 6(Long), 1(Integer)
4. ==> Parameters: 333(Integer), 2(Integer), 6(Long), 1(Integer)
5. <==    Updates: 0
6. 分支线程 -->0
7. <==    Updates: 1
8. 主线程 -->1

② 借助MP

  1. 在属性上加上@Version注解
1. public class Order {
2. // ...
3. @Version
4. private Integer version;
5. }
  1. 配置对应的插件
1. @Configuration
2. public class MybatisPlusConfig {
3. @Bean
4. public MybatisPlusInterceptor versionMybatisPlusInterceptor() {
5. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
6.         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
7. return interceptor;
8.     }
9. }
  1. 测试
1. @Test
2. void testVersion() {
3. new Thread(() -> {
4. Order order = orderMapper.selectById(6L);
5.         order.setPrice(1234);
6. int count = orderMapper.updateById(order);
7.         System.out.println("分支线程 -->"+count);
8.     }).start();
9. Order order = orderMapper.selectById(6L);
10.     order.setPrice(6789);
11. int count = orderMapper.updateById(order);
12.     System.out.println("主线程 -->"+count);
13. }
1. ==>  Preparing: UPDATE t_order SET price=?, version=? WHERE id=? AND version=?
2. ==>  Preparing: UPDATE t_order SET price=?, version=? WHERE id=? AND version=?
3. ==> Parameters: 1234(Integer), 2(Integer), 6(Long), 1(Integer)
4. ==> Parameters: 6789(Integer), 2(Integer), 6(Long), 1(Integer)
5. <==    Updates: 0
6. <==    Updates: 1

11.4 多插件问题

在MP3.4.0版本以后,如果需要配置多个插件的时候要注意:

  • 只需要注入一个MybatisPlusInterceptor对象,然后将插件对象进行添加
  • 需要注意顺序关系,建议使用一下顺序:
  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

例如:

1. @Configuration
2. public class MybatisPlusConfig {
3. 
4. @Bean
5. public MybatisPlusInterceptor MybatisPlusInterceptor() {
6. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
7.         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 分页插件
8.         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件
9. return interceptor;
10.     }
11. }

十二、代码生成器

MP提供的代码生成器按照要求能够自动生成Service层、Controller层、实体类、Mapper层。

1. <dependency>
2. <groupId>com.baomidou</groupId>
3. <artifactId>mybatis-plus-generator</artifactId>
4. <version>3.5.1</version>
5. </dependency>
6. <dependency>
7. <groupId>org.freemarker</groupId>
8. <artifactId>freemarker</artifactId>
9. </dependency>
1. public class MybatisPlusGenerate {
2.     // 项目全路径
3.     public static final String FILE_URL = System.getProperty("user.dir");
4.     public static final String URL = "jdbc:mysql://localhost:3306/jiuxiao";
5.     public static final String USER_NAME = "root";
6.     public static final String PASSWORD = "1234";
7.     public static void main(String[] args) {
8.         FastAutoGenerator.create(URL,USER_NAME,PASSWORD)
9.                 .globalConfig(builder -> {
10.                     builder.author("酒萧") // 设置作者
11.                             .enableSwagger() // 开启 swagger 模式
12.                             .fileOverride() // 覆盖已生成文件
13.                             .outputDir(FILE_URL+"\\order-service\\src\\main\\java"); // 指定输出目录
14.                 })
15.                 .packageConfig(builder -> {
16.                     builder.parent("com.jiuxiao") // 设置父包名
17.                             //.moduleName("mybatisplus") // 设置父包模块名
18.                             //.entity("pojo") // 设置实体类包名
19.                             .pathInfo(Collections.singletonMap(OutputFile.mapperXml, FILE_URL+"\\order-service\\src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径
20.                 })
21.                 .strategyConfig(builder -> {
22.                     builder.addInclude("t_order")// 设置需要生成的表名
23.                             .addTablePrefix("t_", "c_") // 设置过滤表前缀
24.                             .entityBuilder().enableLombok() // 开启lombok
25.                             .enableTableFieldAnnotation() // 开启@tableField
26.                             .controllerBuilder().enableRestStyle() // 开启@RestController
27.                             .serviceBuilder().formatServiceFileName("%sService") // 设置接口名称
28.                             .formatServiceImplFileName("%sServiceImpl").build(); // 设置接口实现类名称
29.                 })
30.                 .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
31.                 .execute();
32.     }
33.
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6月前
|
SQL Java 数据库连接
|
SQL XML Java
|
1月前
|
SQL 缓存 Java
MyBatis系列
MyBatis系列
|
1月前
|
SQL 缓存 Java
Mybatis知识
Mybatis知识
38 0
|
4月前
|
算法 Java 数据库连接
mybatis plus 主键策略
mybatis plus 主键策略
56 2
|
3月前
|
SQL Java 数据库连接
Mybatis02(一)
Mybatis02(一)
29 0
|
4月前
|
SQL Java 数据库连接
Mybatis(一)
Mybatis(一)
37 0
|
6月前
|
SQL Java 数据库连接
MyBatis
MyBatis
41 2
|
11月前
|
Java 数据库连接 数据库
Mybatis及Mybatis-Plus使用
Mybatis及Mybatis-Plus使用
755 2
Mybatis及Mybatis-Plus使用
|
6月前
|
SQL 缓存 Java