MyBatisPlus

一、简述

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

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

官网:MyBatis-Plus

二、快速开始

2.1 设计表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
​
-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'zs', '1234', '张三', 14, '北京');
INSERT INTO `t_user` VALUES (2, 'ls', '1234', '李四', 15, '河南');
​
SET FOREIGN_KEY_CHECKS = 1;
​

2.2 添加依赖


    com.baomidou
    mybatis-plus-boot-starter
    3.5.1

2.3 配置数据源

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/xxx
    username: root
    password: 1234

2.4 创建POJO

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")    
public class User {
    private Integer id;
    private String userName;
    private String password;
    private String nickName;
    private Integer age;
    private String address;
}

2.5 创建Mapper接口

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

public interface UserMapper extends BaseMapper {
}

2.6 创建启动类

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

@SpringBootApplication
@MapperScan("com.jiuxiao.mapper")
public class MPApp {
    public static void main(String[] args) {
        SpringApplication.run(MPApp.class,args);
    }
}

2.7 编写测试方法

@SpringBootTest
public class MPTest {
​
    @Autowired
    UserMapper userMapper;
​
    @Test
    void testSelectAll() {
        List users = userMapper.selectList(null);
        System.out.println(users);
    }
}

三、常用设置

3.1 日志打印

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

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

3.2 设置表映射规则

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

① 单个设置

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

@TableName("t_user")
public class User {
    // ...
}

② 全局设置

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

mybatis-plus:
  global-config:
    db-config:
      table-prefix: t_

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

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

举例:

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

使用前提:

  • SQL中的字段名:全部小写,每个单词之间使用下划线分割

  • java中的属性名:首字母小写,其余单词首字母大写

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

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true

3.4 字段和属性映射

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

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

public class User {
    @TableField("user_name")
    private String userName;
    
    @TableField("nick_name")
    private String nickName;
}

3.5 主键生成策略

  • AUTO

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

  • NONE

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

  • INPUT

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

  • ASSING_ID

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

  • ASSING_UUID

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

① 单个设置

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

  • value:对应的是表中的主键字段名,如果字段名为id,该属性可以省略

  • type:是一个枚举类型,声明主键生成策略

public class User {
    @TableId(value = "id",type = IdType.xxx)
    private Integer id;
}

② 全局设置

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

mybatis-plus:
  global-config:
    db-config:
      id-type: auto

四、CRUD

4.1 增加

  • 传入实体类对象,增加之后通过getter方法可以获取主键值

@Test
void testInsert() {
    User user = new User(null, "ww", "4444", "王五", 16, "河北");
    userMapper.insert(user);
    System.out.println("增加之后的主键 --> "+user.getId());
}

4.2 查询

  • 根据id查询

@Test
void testSelectById() {
    User user = userMapper.selectById(1);
    System.out.println(user);
}

  • 多条件查询

@Test
void testSelectByMap() {
    Map map = new HashMap<>();
    map.put("age",14);
    map.put("address","北京");
    List list = userMapper.selectByMap(map);
    System.out.println(list);
}

  • 批量查询

@Test
void testSelectBatch() {
    List ids = Stream.of(2, 3).collect(Collectors.toList());
    List users = userMapper.selectBatchIds(ids);
    System.out.println(users);
}

4.3 更新

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

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

@Test
void testUpdate() {
    User user = new User();
    user.setId(1);
    user.setAddress("河北");
    user.setAge(66);
    userMapper.updateById(user);
}

4.4 删除

  • 根据id删除

@Test
void testDeleteById() {
    userMapper.deleteById(3);
}

  • 多条件删除

也可以传入对象

@Test
void testDeleteByMap() {
    Map map = new HashMap<>();
    map.put("nick_name","李四");
    map.put("user_name","ls");
    userMapper.deleteByMap(map);
}

  • 批量删除

@Test
void testDeleteBatch() {
    List ids = Stream.of(1, 2).collect(Collectors.toList());
    userMapper.deleteBatchIds(ids);
}

五、条件构造器Wrapper

5.1 概述

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

继承体系:

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

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

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

5.2 AbstractWrapper中常用方法

eq

  • 等于 =

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

allEq

allEq(Map params)
allEq(Map params, boolean null2IsNull)
allEq(boolean condition,BiPredicate filter, Map 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语句

select 
	*
from 
	t_user
where 
	address like '河%' 
and
	age >= 18

使用Wrapper查询

@Test
void testWrapper() {
    QueryWrapper wrapper = new QueryWrapper<>();
    wrapper.likeRight("address","河");
    wrapper.ge("age",18);
    List users = userMapper.selectList(wrapper);
    System.out.println(users);
}

② 示例

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

SQL语句

select 
	*
from 
	t_user
where 
	id in (1,2,3)
and
	age between 20 and 25
and 
	`password` = '1234'

使用Wrapper查询

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

③ 示例

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

SQL语句

select 
	*
from 
	t_user
where 
	`address` like '%北%'
and 
	nick_name is not null
order by
 id desc

使用Wrapper查询

@Test
void testWrapper() {
    QueryWrapper wrapper = new QueryWrapper<>();
    wrapper.like("address","北");
    wrapper.isNotNull("nick_name");
    wrapper.orderByDesc("id");
    List users = userMapper.selectList(wrapper);
    System.out.println(users);
}

5.3 QueryWrapper

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

① 方法一

select(String... sqlSelect);

参数

  • 查询的字段

@Test
void testQueryWrapper() {
    QueryWrapper wrapper = new QueryWrapper<>();
    wrapper.select("id","nick_name","address");
    List users = userMapper.selectList(wrapper);
    System.out.println(users);
}

② 方法二

select(Predicate predicate);

参数

  • 需要过滤的字段【无法过滤id】

@Test
void testQueryWrapper() {
    QueryWrapper wrapper = new QueryWrapper<>(new User());
    wrapper.select(tableFieldInfo -> "nick_name".equals(tableFieldInfo.getColumn()) || "address".equals(tableFieldInfo.getColumn()));
    List users = userMapper.selectList(wrapper);
    System.out.println(users);
}

③ 方法三

select(Class entityClass, Predicate predicate);

参数

  • 实体类字节码

  • 需要过滤的字段【无法过滤id】

@Test
void testQueryWrapper() {
    QueryWrapper wrapper = new QueryWrapper<>();
    wrapper.select(User.class,tableFieldInfo -> !"user_name".equals(tableFieldInfo.getColumn()));
    List users = userMapper.selectList(wrapper);
    System.out.println(users);
}

5.4 UpdateWrapper

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

@Test
void testUpdateWrapper() {
    UpdateWrapper wrapper = new UpdateWrapper<>();
    wrapper.gt("age",18);
    wrapper.set("password","6666");
    userMapper.update(new User(),wrapper);
}

5.5 LambdaWrapper

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

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

@Test
void testLambdaWrapper() {
    LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
    wrapper.likeRight(User::getAddress,"河");
    wrapper.ge(User::getAge,"18");
    wrapper.select(User::getUserName,User::getAddress);
    List users = userMapper.selectList(wrapper);
    System.out.println(users);
}

六、Mapper层自定义方法

6.1 概述

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

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

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

6.2 Mybatis方式

① 定义接口方法

public interface UserMapper extends BaseMapper {

   User selectById(Integer id);
}

② 定义xml文件





③ 编写SQL语句


    id,user_name,password,nick_name,age,address

6.3 使用条件构造器

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

  1. 方法定义中将Wrapper以参数传入,并指定其参数名

public interface UserMapper extends BaseMapper {

   List selectByWrapper(@Param(Constants.WRAPPER) Wrapper wrapper);
}

  1. 在SQL语句中将Wrapper中的SQL片段进行拼接

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

七、分页查询

7.1 单表分页查询

  1. 配置分页查询拦截器

@Configuration
public class MybatisPlusConfig {
	@Bean
    public MybatisPlusInterceptor MybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

  1. 测试分页查询

@Test
void testPage() {
    IPage page = new Page<>();
    // 设置每页条数
    page.setSize(3);
    // 设置查询页数
    page.setCurrent(1);
    page = userMapper.selectPage(page,null);
    System.out.println(page.getRecords()); // 获取每页数据
    System.out.println(page.getTotal()); // 获取总条数
}

7.2 多表分页查询

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

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

  1. 表设计

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `price` int(11) NULL DEFAULT NULL COMMENT '价格',
  `user_id` int(11) NULL DEFAULT NULL COMMENT '创建人id',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',
  `version` int(11) NULL DEFAULT 1 COMMENT '版本号',
  `del_flag` int(1) NULL DEFAULT 1 COMMENT '1-已删除 0-未删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_order
-- ----------------------------
INSERT INTO `t_order` VALUES (1, 200, 1, '2022-10-19 21:28:52', '2022-10-22 21:28:54', 1, 0);
INSERT INTO `t_order` VALUES (2, 100, 1, '2022-10-08 21:29:11', '2022-10-18 21:29:15', 1, 0);
INSERT INTO `t_order` VALUES (3, 15, 2, '2022-09-29 21:29:39', '2022-10-15 21:29:43', 1, 0);

SET FOREIGN_KEY_CHECKS = 1;

  1. 定义实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private Long id;
    private Integer price;
    private Integer userId;
    private Date createTime;
    private Date updateTime;
    private Integer version;
    private Integer delFlag;
}

  1. 定义Mapper接口

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

public interface UserMapper extends BaseMapper {

   IPage selectByPage(Page page);
}

  1. 编写SQL语句:


    
    
    
    
    
    
    

  1. 分步查询OrderMapper接口

public interface OrderMapper{

    @Select("select * from t_order where user_id = #{id}")
    List selectByUserId(Integer id);
}

  1. 测试

@Test
void testPageMethod() {
    IPage page = new Page<>();
    page.setSize(2);
    page.setCurrent(1);
    page = userMapper.selectByPage(page);
    System.out.println(page.getRecords());
    System.out.println(page.getTotal());
}

八、Service接口

8.1 概述

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

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

8.2 使用

  1. 接口

public interface UserService extends IService {
    
}

  1. 实现类

@Service
public class UserServiceImpl extends ServiceImpl implements UserService {

}

  1. 测试

@Autowired
UserService userService;

@Test
void testServiceMethod() {
    User user = userService.getById(3);
    System.out.println(user);
}

其他方法查看官方文档

8.3 自定义Service层方法

  1. 接口

public interface UserService extends IService {

    User selectByIdOfOrder(Integer id);
}

  1. 实现类

@Service
public class UserServiceImpl extends ServiceImpl implements UserService {

    @Autowired
    OrderMapper orderMapper;

    @Override
    public User selectByIdOfOrder(Integer id) {
        // 获取mapper
        UserMapper userMapper = baseMapper;
        User user = userMapper.selectById(1);
        List orders = orderMapper.selectByUserId(user.getId());
        user.setList(orders);
        return user;
    }
}

  1. 测试

@Autowired
UserService userService;

@Test
void testCustomService() {
    User user = userService.selectByIdOfOrder(1);
    System.out.println(user);
}

九、自动填充

9.1 概述

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

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

9.2 使用

① 添加注解

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

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

  • INSERT:进行增加操作时自动填充

  • UPDATE:进行更新操作时自动填充

  • INSERT_UPDATE:进行增加和更新时自动填充

  • DEFAULT:默认不处理

public class Order {
    // ...
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.UPDATE)
    private Date updateTime;
}

② 自定义填充处理器 MyMetaObjectHandler

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        // this.setFieldValByName("createTime",LocalDateTime.now(),metaObject);
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // this.setFieldValByName("updateTime",LocalDateTime.now(),metaObject);
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

注意:

  • 未注释的地方,时间类型只能是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注解

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: delFlag # 实体类属性名
      logic-delete-value: 1 # 逻辑已删除值(默认是1)
      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 演示冲突

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

@Test
void testVersion() {
    new Thread(() -> {
        Order order = new Order();
        order.setId(6L);
        order.setPrice(55);
        int count = orderMapper.updateById(order);
        System.out.println("分支线程 -->"+count);
    }).start();
    Order order = new Order();
    order.setId(6L);
    order.setPrice(66);
    int count = orderMapper.updateById(order);
    System.out.println("主线程 -->"+count);
}

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

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

11.3 使用乐观锁解决

① 手动解决

@Test
void testVersion() {
    new Thread(() -> {
        Order order = orderMapper.selectById(6L);
        UpdateWrapper wrapper = new UpdateWrapper<>();
        wrapper.set("price",333);
        wrapper.set("version",order.getVersion()+1);
        wrapper.eq("id",order.getId());
        wrapper.eq("version",order.getVersion());
        int count = orderMapper.update(new Order(), wrapper);
        System.out.println("分支线程 -->"+count);
    }).start();
    Order order = orderMapper.selectById(6L);
    UpdateWrapper wrapper = new UpdateWrapper<>();
    wrapper.set("price",222);
    wrapper.set("version",order.getVersion()+1);
    wrapper.eq("id",order.getId());
    wrapper.eq("version",order.getVersion());
    int count = orderMapper.update(new Order(), wrapper);
    System.out.println("主线程 -->"+count);
}
==>  Preparing: UPDATE t_order SET price=?,version=? WHERE (id = ? AND version = ?)
==>  Preparing: UPDATE t_order SET price=?,version=? WHERE (id = ? AND version = ?)
==> Parameters: 222(Integer), 2(Integer), 6(Long), 1(Integer)
==> Parameters: 333(Integer), 2(Integer), 6(Long), 1(Integer)
<==    Updates: 0
分支线程 -->0
<==    Updates: 1
主线程 -->1

② 借助MP

  1. 在属性上加上@Version注解

public class Order {
    // ...
    @Version
    private Integer version;
}

  1. 配置对应的插件

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor versionMybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

  1. 测试

@Test
void testVersion() {
    new Thread(() -> {
        Order order = orderMapper.selectById(6L);
        order.setPrice(1234);
        int count = orderMapper.updateById(order);
        System.out.println("分支线程 -->"+count);
    }).start();
    Order order = orderMapper.selectById(6L);
    order.setPrice(6789);
    int count = orderMapper.updateById(order);
    System.out.println("主线程 -->"+count);
}
==>  Preparing: UPDATE t_order SET price=?, version=? WHERE id=? AND version=?
==>  Preparing: UPDATE t_order SET price=?, version=? WHERE id=? AND version=?
==> Parameters: 1234(Integer), 2(Integer), 6(Long), 1(Integer)
==> Parameters: 6789(Integer), 2(Integer), 6(Long), 1(Integer)
<==    Updates: 0
<==    Updates: 1

11.4 多插件问题

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

  • 只需要注入一个MybatisPlusInterceptor对象,然后将插件对象进行添加

  • 需要注意顺序关系,建议使用一下顺序:

    • 多租户,动态表名

    • 分页,乐观锁

    • sql 性能规范,防止全表更新与删除

例如:

@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor MybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 分页插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 乐观锁插件
        return interceptor;
    }
}

十二、代码生成器

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


    com.baomidou
    mybatis-plus-generator
    3.5.1


    org.freemarker
    freemarker
public class MybatisPlusGenerate {
    // 项目全路径
    public static final String FILE_URL = System.getProperty("user.dir");
    public static final String URL = "jdbc:mysql://localhost:3306/jiuxiao";
    public static final String USER_NAME = "root";
    public static final String PASSWORD = "1234";
    public static void main(String[] args) {
        FastAutoGenerator.create(URL,USER_NAME,PASSWORD)
                .globalConfig(builder -> {
                    builder.author("酒萧") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir(FILE_URL+"\\order-service\\src\\main\\java"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.jiuxiao") // 设置父包名
                            //.moduleName("mybatisplus") // 设置父包模块名
                            //.entity("pojo") // 设置实体类包名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, FILE_URL+"\\order-service\\src\\main\\resources\\mapper\\")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_order")// 设置需要生成的表名
                            .addTablePrefix("t_", "c_") // 设置过滤表前缀
                            .entityBuilder().enableLombok() // 开启lombok
                            .enableTableFieldAnnotation() // 开启@tableField
                            .controllerBuilder().enableRestStyle() // 开启@RestController
                            .serviceBuilder().formatServiceFileName("%sService") // 设置接口名称
                            .formatServiceImplFileName("%sServiceImpl").build(); // 设置接口实现类名称
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

你可能感兴趣的:(mybatis,java,数据库)