Mybatis Plus学习笔记

Mybatis Plus

学习MyBatis-Plus之前要先学Mybatis –> Spring —> Spring MVC

为什么要学它?Mybatis Plus可以节省我们大量的时间,所有CRUD代码都可以自动完成,JPA,tk-mapper,Mybatis Plus

1、简介

什么是Mybatis Plus?

Mybatis本来就是简化JDBC操作的!MyBatis Plus(简称 MP),是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

愿景

我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗中的 1P、2P,基友搭配,效率翻倍。

官网:MyBatis-Plus (baomidou.com)

支持数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。

  • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

框架结构

2、特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

1、快速入门

地址https://baomidou.com/pages/226c21/#初始化工程

使用第三方插件:

  1. 导入对应的依赖
  2. 研究依赖如何配置
  3. 代码如何编写
  4. 提高扩展技术能力

步骤

1.创建数据库 mybatis_plus

2.创建数据库

创建表

 DROP TABLE IF EXISTS user;
 CREATE TABLE user
 (
     id BIGINT(20) NOT NULL COMMENT '主键ID',
     name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
     age INT(11) NULL DEFAULT NULL COMMENT '年龄',
     email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
     PRIMARY KEY (id)
 );
 --真实开发环境中,一般都会添加以下:version(乐观锁),deleted(逻辑删除),gmt_create,gmt_modified

插入数据

 INSERT INTO user (id, name, age, email) VALUES
 (1, 'Jone', 18, 'test1@baomidou.com'),
 (2, 'Jack', 20, 'test2@baomidou.com'),
 (3, 'Tom', 28, 'test3@baomidou.com'),
 (4, 'Sandy', 21, 'test4@baomidou.com'),
 (5, 'Billie', 24, 'test5@baomidou.com');

3.编写项目,初始化项目! 使用SpringBoot初始化!

4.导入依赖

<dependencies>
    <!-- spring-boot-web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- spring-boot-test -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!-- mybatis plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.2.11</version>
    </dependency>
</dependencies>

说明:使用Mybatis-plus可以节省我们大量的代码,尽量不要同时导入Mybatis和Mybatis-plus避免版本冲突!

5.连接数据库!这一步和mybatis相同!

application.yml

server:
  port: 8888
# mysql 8 驱动不同 com.mysql.cj.jdbc.Driver 需要增加时区的配置 serverTimezone=UTC
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver

# mysql 5 驱动不同  com.mysql.jdbc.Driver
#spring:
#  datasource:
#    username: root
#    password: 123456
#    url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
#    driver-class-name: com.mysql.jdbc.Driver

查看驱动版本(注:高版本可兼容低版本,需要配置时区,否则无法运行)

6.传统的方式pojo-mapper(连接mybatis,配置mapper.xml文件)-service-controller

使用了mybatis-plus之后

  • pojo

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        private Long id;   //对应包装类
        private String name;
        private Integer age;
        private String email;
    
    }
    
  • mapper接口

    UserMapper

    //在对应的mapper上面继承基本的类 BaseMapper
    @Mapper//不写需要在启动类扫描Mapper
    @Repository//代表持久层
    public interface UserMapper extends BaseMapper<User> {
        //所有CRUD操作都已经编写完成了
        //你不需要向以前一样配置一大堆文件了!
    }
    

    注意点:不写@Mapper注解需要在主启动类MybatisPlusProjectApplication上扫描我们Mapper包下的所有接口

    MybatisPlusProjectApplication

    @SpringBootApplication
    @MapperScan("com.mian.mapper")//Mapper接口添加@Mapper此处可省略
    public class MybatisPlusProjectApplication {
        public static void main(String[] args) {
            SpringApplication.run(MybatisPlusProjectApplication.class, args);
        }
    }
    
  • 测试类中进行测试

    @SpringBootTest
    public class MybatisPlusProjectTest {
    
        //继承了BaseMapper, 所有的方法都来自父类
        //我们也可以编写自己的扩展方法
        private UserMapper userMapper;
    
        @Autowired
        public MybatisPlusProjectTest(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    
        @Test
        void contextLoads() {
            //参数是一个Wrapper , 条件构造器,这里我们先不用,先写为null
            //查询全部用户
            List<User> users = userMapper.selectList(null);
            users.forEach(user -> {
                System.out.println(user);
            });
        }
    
    }
    
  • 查询结果

思考问题

  1. sql是谁帮我们写的?—mybatis-plus
  2. 方法是谁帮我们写的?—mybatis-plus

2、配置日志

我们所有的sql是不可见的,我们希望知道他是怎么执行的,所以我们必须看日志!

application.yml

server:
  port: 8888
# mysql 8 驱动不同 com.mysql.cj.jdbc.Driver 需要增加时区的配置 serverTimezone=UTC
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver

# mysql 5 驱动不同  com.mysql.jdbc.Driver
#spring:
#  datasource:
#    username: root
#    password: 123456
#    url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
#    driver-class-name: com.mysql.jdbc.Driver
# 配置日志  (默认控制台输出)
mybatis-plus:
  configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

配置完日志之后你会喜欢上mybatis-plus!

3、CRUD扩展

1、插入数据

@Test
public void insertTest() {
    try {
        User user = User.class.newInstance();
        user.setName("张三");
        user.setAge(22);
        user.setEmail("123456@email.com");
        int result = userMapper.insert(user);//id自动生成
        System.out.println(result);
        List<User> users = userMapper.selectList(null);
        users.forEach(userResult -> {
            System.out.println(userResult);
        });
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }

}

注意点:数据库插入的id默认值为:全局的唯一id @TableId(type = IdType.ID_WORKER) //默认全局唯一id ,不加默认生成

此处使用为@TableId(type = IdType.ASSIGN_ID),新版本取消了ID_WORKER,默认使用雪花算法生成

2、主键生成策略 @TableId对应主键

分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法😦Twitter的snowflake算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0,可以保证几乎全球唯一

默认全局唯一

(过时,已取消,推荐使用雪花算法)

@TableId(type = IdType.ID_WORKER) //默认全局唯一id ,不加默认生成

主键自增

我们需要配置主键自增:

  1. 实体类字段上@TableId(type = IdType.AUTO)
  2. 数据库字段一定要是自增!

再次插入

@Test
public void insertTest2() {
    try {
        User user = User.class.newInstance();
        user.setName("李四");
        user.setAge(23);
        user.setEmail("123456@email.com");
        int result = userMapper.insert(user);//id自动生成
        System.out.println(result);
        List<User> users = userMapper.selectList(null);
        users.forEach(userResult -> {
            System.out.println(userResult);
        });
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }

}

其余源码解释

public enum IdType {
    AUTO(0), //数据库自增ID
    NONE(1), //该类型为未设置主键类型
    INPUT(2), //用户输入ID
    			//该类型可以通过自己注册自动填充插件进行
    //以下类型、只有当插入对象ID 为空,才自动填充。  
    ASSIGN_ID(3), //全局唯一ID(雪花算法) 推荐使用 可排序
    ASSIGN_UUID(4); //全局唯一ID(UUID) 不推荐 无序
    //ID_WORKER(3),//全局唯一ID (idWorker)新版已被取消,使用雪花算法

3、更新数据

注意:updateById()参数是 一个对象!

@Test
public void updateTest() {
    try {
        User user = User.class.newInstance();
        user.setId(1565862102255124483L);
        user.setName("王五");
        user.setAge(28);
        user.setEmail("111111@gmail.com");
        int result = userMapper.updateById(user);
        System.out.println(result);
        List<User> users = userMapper.selectList(null);
        users.forEach(userResult -> {
            System.out.println(userResult);
        });
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

所有的sql都是动态帮你配置的

4、自动填充

创建时间,修改时间! 这些个操作都是自动化完成的,我们不希望手动更新!

阿里巴巴开发手册:所有的数据库表:gmt_create .gmt_modified几乎所有的表都要配置上!而且需要自动化!

方式一:数据库级别(工作中不建议)

1.在表中新增字段 create_time , update_time

2.再次测试插入方法,我们需要先把实体类同步

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;   //对应包装类
    private String name;
    private Integer age;
    private String email;
    private Date createTime;
    private Date updateTime;

}

再次更新查看结果即可

@Test
public void updateTest() {
    try {
        User user = User.class.newInstance();
        user.setId(1565862102255124483L);
        user.setName("王五");
        user.setAge(28);
        user.setEmail("111111@gmail.com");
        int result = userMapper.updateById(user);
        System.out.println(result);
        List<User> users = userMapper.selectList(null);
        users.forEach(userResult -> {
            System.out.println(userResult);
        });
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

方式二:代码级别

不是所有人都能直接操作数据库

1.删除数据库默认值

2.实体类字段属性上添加注解 @TableField

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;   //对应包装类
    private String name;
    private Integer age;
    private String email;
    //记住用util包下的Date!!
    //字段添加填充内容            在插入的时候字段填充
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    //在插入和更新的时候字段填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

}

3.编写处理器来处理这个注解

建立一个handler包,在包下建立自己的MyMetaObjectHandler

// 需要继承MetaObjectHandler并且重写里面的两个方法insertFill,updateFill
@Log4j2 //日志
@Configuration //加入到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {

    //插入时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("Start insert fill.... ");
        //设置字段的值               想要修改的字段名   插入的值
        //this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        // 或者
        this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        //this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }

    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("Start update fill.... ");
        //this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
        // 或者
        this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        //this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }

}

4.测试插入

@Test
public void insertTest2() {
    try {
        User user = User.class.newInstance();
        user.setName("李四");
        user.setAge(23);
        user.setEmail("123456@email.com");
        int result = userMapper.insert(user);//id自动生成
        System.out.println(result);
        List<User> users = userMapper.selectList(null);
        users.forEach(userResult -> {
            System.out.println(userResult);
        });
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }

}

5测试更新,观察时间即可!

5、乐观锁&悲观锁

乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试

悲观锁:顾名思义十分悲观,他总是认为出现问题,无论干什么都会上锁!再去操作!

我们这里主要讲解 乐观锁机制!

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时,set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

实现线程通信的安全

乐观锁:1、先查询,获得版本号 version = 1   
-- A
update user set name = "张三", version = version + 1
where id = 2 and version = 1

--B线程抢先完成,这个时候version = 2,会导致A修改失败!
update user set name = "李四" , version = version + 1
where id = 2 and version = 1 

测试

1、添加version字段

2、添加对应实体类字段

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;   //对应包装类
    private String name;
    private Integer age;
    private String email;
    @Version //乐观锁注解
    private Integer version;
    //记住用util包下的Date!!
    //字段添加填充内容            在插入的时候字段填充
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    //在插入和更新的时候字段填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

}

3、注册组件

//可以将主启动类上的扫描包注解放到这里
@MapperScan("com.mian.mapper") //扫描mapper接口路径
@EnableTransactionManagement   //自动管理事务
@Configuration //代表这是一个配置类
public class MybatisPlusConfig {

    // 注册乐观锁插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

}

4、测试

首先执行查询

更新 注意version

@Test
public void versionTest2() {
    try {
        //线程1
        User user1 = userMapper.selectById(1565862102255124489L);
        user1.setName("赵六");
        //线程2:模拟另一个线程执行插队操作
        User user2 = userMapper.selectById(1565862102255124489L);
        user1.setName("孙七");
        userMapper.updateById(user2);
        //在线程1执行更新操作之前,线程2抢先更新了,使得version变为了2,线程1更新失败,如果没有乐观锁就会覆盖插队线程的值
        userMapper.updateById(user1);
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

6、查询操作

通过ID查询单条

@Test
public void queryByIdTest() {
    User user = userMapper.selectById(1565862102255124489L);
    System.out.println(user);
}

通过ID批量查询

@Test
public void queryByIdsTest() {
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
    users.forEach(System.out::println);
}

按条件查询 - 使用Map操作

@Test
public void queryByMapTest() {
    Map<String, Object> map = new HashMap<>();
    map.put("name", "Jack");
    map.put("age", "20");
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

7、分页查询

分页在网站使用的十分之多!

  • 1、原始的limit进行分页
  • 2、pageHelper第三方插件
  • 3、mybatisPlus其实也内置了分页插件!

如何使用!

1、配置拦截器组件即可

//可以将主启动类上的扫描包注解放到这里
@MapperScan("com.mian.mapper") //扫描mapper接口路径
@EnableTransactionManagement   //自动管理事务
@Configuration //代表这是一个配置类
public class MybatisPlusConfig {

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

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */

    //@Bean
    //public ConfigurationCustomizer configurationCustomizer() {
    //    return configuration -> configuration.setUseDeprecatedExecutor(false);
    //}

}

2、直接使用page对象即可

分页查询

@Test
public void queryPageTest() {
    //参数一:查询的页面
    //参数二:页面大小
    Page<User> page = new Page<>(1, 3);
    //需要两个参数:IPage<T> selectPage(IPage<T> var1, @Param("ew") Wrapper<T> var2);
    page = userMapper.selectPage(page, null);
    page.getRecords().forEach(System.out::println);
    System.out.println(page.getTotal());
    // page.hasPrevious();//是否有上一页
    // page.hasNext();//是否有下一页
    // page.getTotal();//得到总数
    // page.getRecords();//得到记录
    // page.getSize();//得到大小
    // page.getCurrent();//得到参数
}

8、删除操作

通过id删除

@Test
public void deleteByIdTest() {
    userMapper.deleteById(1565862102255124489L);
}

通过id批量删除

@Test
public void deleteByIdsTest() {
    userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
}

通过条件删除

@Test
public void deleteByMapTest() {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "赵六");
    userMapper.deleteByMap(map);
}

9、逻辑删除

物理删除:从数据库中直接移除

逻辑删除: 在数据库中没有被移除,而是通过一个变量来让他失效!

管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!

测试:

1.在数据表中增加一个deleted字段

2.实体类中增加属性

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;   //对应包装类
    private String name;
    private Integer age;
    private String email;
    @Version //乐观锁注解
    private Integer version;
    @TableLogic
    private Integer deleted;
    //记住用util包下的Date!!
    //字段添加填充内容            在插入的时候字段填充
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    //在插入和更新的时候字段填充
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

}

3.配置

旧版需要修改配置类

//逻辑删除
@Bean
public ISqlInjector sqlInjector(){
    return new LogicSqlInjector();
}

新版本直接修改application.yml

mybatis-plus:
  configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 0 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 1 # 逻辑未删除值(默认为 0)

测试删除

通过ID删除

@Test
public void deleteByIdTest() {
    userMapper.deleteById(1565862102255124489L);
}

本质走的是更新操作,不是删除操作

测试查询

@Test
public void queryByIdTest() {
    User user = userMapper.selectById(1565862102255124489L);
    System.out.println(user);
}

查询时候会自动过滤删除的数据

4、性能分析插件

我们在平时的开发中,会遇到一些慢sql.

作用:性能分析拦截器,用于输出每条SQL语句及其执行时间

mybatisplus也提供了性能分析插件,如果超过这个时间就停止运行!

Mybatis Plus 3.2后已移除,需要自行使用第三方插件

3.2以前使用

1.导入插件

MybatisPlusConfig.java中添加如下配置

//性能分析
@Bean
@Profile({"dev","test"}) //设置dev 和 test环境开启
public PerformanceInterceptor performanceInterceptor(){
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(100); // ms  设置sql执行的最大时间,超过不执行sql
    performanceInterceptor.setFormat(true); // 开启sql的格式化支持
    return performanceInterceptor;
}

记住在SpringBoot中配置环境为 dev或者test环境

application.yml中添加设置开发环境

# 设置开发环境 当前激活环境dev
spring:
 profiles: active=dev

2.测试查询

@Test
void contextLoads() {
    //参数是一个Wrapper , 条件构造器,这里我们先不用,先写为null
    //查询全部用户
    List<User> users = userMapper.selectList(null);
    for (User user : users) {
        System.out.println(user);
    }
}

5、条件构造器

@Test
public void queryByQueryWrapper() {
    // 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
    // 查询一般用QueryWrapper
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.isNotNull(User::getName)
        .isNotNull(User::getEmail)
        .ge(User::getAge, 12);
    List<User> users = userMapper.selectList(queryWrapper);// 和我们刚才学习的map对比一下
    users.forEach(System.out::println);
}

@Test
public void queryByQueryWrapper2() {
    // 查询name为Jack
    // 查询一般用QueryWrapper
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(User::getName, "Jack");
    User user = userMapper.selectOne(queryWrapper);
    System.out.println(user);
}
@Test
public void queryByQueryWrapper3() {
    // 查询age在20~30之间的用户
    // 查询一般用QueryWrapper
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.between(User::getAge, 20, 30);
    Long count = userMapper.selectCount(queryWrapper);
    System.out.println(count);
}
@Test
public void queryByQueryWrapper4() {
    // 查询name中没有e的 且email以t为开头的
    // 查询一般用QueryWrapper
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.notLike(User::getName, "e")
        .likeRight(User::getEmail, "t");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}
@Test
public void queryByQueryWrapper5() {
    // 子查询
    // 查询一般用QueryWrapper
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.inSql(User::getId, "select id from user where id < 3");
    List<Object> objects = userMapper.selectObjs(queryWrapper);
    objects.forEach(System.out::println);
}
@Test
public void queryByQueryWrapper6() {
    // 排序
    // 查询一般用QueryWrapper
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.orderByAsc(User::getName);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

6、代码自动生成器

mapper、pojo、service、controller都自动生成!

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper.xml、Service、Controller 等各个模块的代码,极大的提升了开发效率。

导入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.2</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>

只需要改实体类名字 和包名 还有 数据库配置即可

public class MianCode {
    public static void main(String[] args) {
        String projectPath = System.getProperty("user.dir");
        String url = "jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false";
        String username = "root";
        String password = "123456";

        FastAutoGenerator
            //数据库配置(DataSourceConfig)
            .create(
            new DataSourceConfig.Builder(url, username, password)//jdbc路径,数据库账号,数据库密码
            //可选配置
            .dbQuery(new MySqlQuery())//数据库查询
            .schema("mybatis-plus")//数据库 schema(部分数据库适用)
            .typeConvert(new MySqlTypeConvert())//数据库类型转换器
            .keyWordsHandler(new MySqlKeyWordsHandler())//数据库关键字处理器
        )
            //全局配置(GlobalConfig)
            .globalConfig(builder -> {
                builder
                    .fileOverride()//覆盖已生成文件
                    .disableOpenDir()//禁止打开输出目录
                    .outputDir(projectPath + "/src/main/java") //指定输出目录
                    .author("mian")//设置作者
                    //.enableKotlin()//开启 kotlin 模式
                    .enableSwagger()//开启 swagger 模式
                    .dateType(DateType.TIME_PACK)//时间策略
                    .commentDate("yyyy-MM-dd")//时间策略
                    .build();
            })
            //包配置(PackageConfig)
            .packageConfig(builder -> {
                builder
                    .parent("com.mian") //父包名
                    .moduleName("sys") //父包模块名
                    .entity("pojo")//Entity 包名
                    .service("service")//Service 包名
                    .serviceImpl("service.impl")//Service 包名
                    .mapper("mapper")//Mapper 包名
                    .xml("mapper.xml")//Mapper XML 包名
                    .controller("controller")//Controller 包名
                    .other("other")//自定义文件包名
                    .pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mybatis/mapper")); //设置mapperXml路径配置信息
            })
            /**
                 * 模板配置(TemplateConfig)
                 * 方法	                    说明	                    示例
                 * disable	                禁用所有模板
                 * disable(TemplateType...)	禁用模板	                TemplateType.ENTITY
                 * entity(String)	        设置实体模板路径(JAVA)	    /templates/entity.java
                 * entityKt(String)	        设置实体模板路径(kotlin)	/templates/entity.java
                 * service(String)	        设置 service 模板路径	    /templates/service.java
                 * serviceImpl(String)	    设置 serviceImpl 模板路径	/templates/serviceImpl.java
                 * mapper(String)	        设置 mapper 模板路径	    /templates/mapper.java
                 * mapperXml(String)	    设置 mapperXml 模板路径	/templates/mapper.xml
                 * controller(String)	    设置 controller 模板路径	/templates/controller.java
                 */
            .templateEngine(new FreemarkerTemplateEngine())//使用Freemarker引擎模板,默认的是Velocity引擎模板
            /**
                 * 注入配置(InjectionConfig)
                 * 方法	                                                        说明	                示例
                 * beforeOutputFile(BiConsumer<TableInfo, Map<String, Object>>)	输出文件之前消费者
                 * customMap(Map<String, Object>)	                            自定义配置 Map 对象	Collections.singletonMap("test", "baomidou")
                 * customFile(Map<String, String>)	                            自定义配置模板文件	    Collections.singletonMap("test.txt", "/templates/test.vm")
                 */
            //策略配置(StrategyConfig)
            /**
                 * 方法	                        说明	                    示例
                 * enableCapitalMode	        开启大写命名	            默认值:false
                 * enableSkipView	            开启跳过视图	            默认值:false
                 * disableSqlFilter	            禁用 sql 过滤	        默认值:true,语法不能支持使用 sql 过滤表的话,可以考虑关闭此开关
                 * enableSchema	                启用 schema	            默认值:false,多 schema 场景的时候打开
                 * likeTable(LikeTable)	        模糊表匹配(sql 过滤)	    likeTable 与 notLikeTable 只能配置一项
                 * notLikeTable(LikeTable)	    模糊表排除(sql 过滤)	    likeTable 与 notLikeTable 只能配置一项
                 * addInclude(String...)	    增加表匹配(内存过滤)	    include 与 exclude 只能配置一项
                 * addExclude(String...)	    增加表排除匹配(内存过滤)	include 与 exclude 只能配置一项
                 * addTablePrefix(String...)	增加过滤表前缀
                 * addTableSuffix(String...)	增加过滤表后缀
                 * addFieldPrefix(String...)	增加过滤字段前缀
                 * addFieldSuffix(String...)	增加过滤字段后缀
                 * entityBuilder	            实体策略配置
                 * controllerBuilder	        controller 策略配置
                 * mapperBuilder	            mapper 策略配置
                 * serviceBuilder	            service 策略配置
                 */
            /**
                 * Entity 策略配置
                 * 方法	                                说明	                            示例
                 * nameConvert(INameConvert)	        名称转换实现
                 * superClass(Class<?>)	                设置父类	                        BaseEntity.class
                 * superClass(String)	                设置父类	                        com.baomidou.global.BaseEntity
                 * disableSerialVersionUID	            禁用生成 serialVersionUID	    默认值:true
                 * enableColumnConstant	                开启生成字段常量	                默认值:false
                 * enableChainModel	                    开启链式模型	                    默认值:false
                 * enableLombok	                        开启 lombok 模型	                默认值:false
                 * enableRemoveIsPrefix	                开启 Boolean 类型字段移除 is 前缀	默认值:false
                 * enableTableFieldAnnotation	        开启生成实体时生成字段注解	        默认值:false
                 * enableActiveRecord	                开启 ActiveRecord 模型	        默认值:false
                 * versionColumnName(String)	        乐观锁字段名(数据库)
                 * versionPropertyName(String)	        乐观锁属性名(实体)
                 * logicDeleteColumnName(String)	    逻辑删除字段名(数据库)
                 * logicDeletePropertyName(String)	    逻辑删除属性名(实体)
                 * naming	                            数据库表映射到实体的命名策略	        默认下划线转驼峰命名:NamingStrategy.underline_to_camel
                 * columnNaming	                        数据库表字段映射到实体的命名策略	    默认为 null,未指定按照 naming 执行
                 * addSuperEntityColumns(String...)	    添加父类公共字段
                 * addIgnoreColumns(String...)	        添加忽略字段
                 * addTableFills(IFill...)	            添加表字段填充
                 * addTableFills(List<IFill>)	        添加表字段填充
                 * idType(IdType)	                    全局主键类型
                 * convertFileName(ConverterFileName)	转换文件名称
                 * formatFileName(String)	            格式化文件名称
                 */
            /**
                 * Controller 策略配置
                 * 方法	                                说明	                        示例
                 * superClass(Class<?>)	                设置父类	                    BaseController.class
                 * superClass(String)	                设置父类	                    com.baomidou.global.BaseController
                 * enableHyphenStyle	                开启驼峰转连字符	            默认值:false
                 * enableRestStyle	                    开启生成@RestController 控制器	默认值:false
                 * convertFileName(ConverterFileName)	转换文件名称
                 * formatFileName(String)	            格式化文件名称
                 */
            /**
                 * Service 策略配置
                 * 方法	                                            说明	                        示例
                 * superServiceClass(Class<?>)	                    设置 service 接口父类	        BaseService.class
                 * superServiceClass(String)	                    设置 service 接口父类	        com.baomidou.global.BaseService
                 * superServiceImplClass(Class<?>)	                设置 service 实现类父类	    BaseServiceImpl.class
                 * superServiceImplClass(String)	                设置 service 实现类父类	    com.baomidou.global.BaseServiceImpl
                 * convertServiceFileName(ConverterFileName)	    转换 service 接口文件名称
                 * convertServiceImplFileName(ConverterFileName)	转换 service 实现类文件名称
                 * formatServiceFileName(String)	                格式化 service 接口文件名称
                 * formatServiceImplFileName(String)	            格式化 service 实现类文件名称
                 */
            /**
                 * Mapper 策略配置
                 * 方法	                                    说明	                    示例
                 * superClass(Class<?>)	                    设置父类	                BaseMapper.class
                 * superClass(String)	                    设置父类	                com.baomidou.global.BaseMapper
                 * enableMapperAnnotation	                开启 @Mapper 注解	    默认值:false
                 * enableBaseResultMap	                    启用 BaseResultMap 生成	默认值:false
                 * enableBaseColumnList	                    启用 BaseColumnList	    默认值:false
                 * cache(Class<? extends Cache>)	        设置缓存实现类	            MyMapperCache.class
                 * convertMapperFileName(ConverterFileName)	转换 mapper 类文件名称
                 * convertXmlFileName(ConverterFileName)	转换 xml 文件名称
                 * formatMapperFileName(String)	            格式化 mapper 文件名称
                 * formatXmlFileName(String)	            格式化 xml 实现类文件名称
                 */
            .strategyConfig(builder -> {
                builder
                    .addInclude("user")//设置需要生成的表名
                    .addTablePrefix("t_", "c_") //设置过滤表前缀
                    //.addFieldSuffix("_flag")//增加过滤表后缀
                    .entityBuilder()//实体策略配置
                    .enableLombok()//开启 lombok 模型
                    .enableTableFieldAnnotation()//开启生成实体时生成字段注解
                    .versionColumnName("version")//乐观锁字段名(数据库)
                    .versionPropertyName("version")//乐观锁属性名(实体)
                    .logicDeleteColumnName("deleted")//逻辑删除字段名(数据库)
                    .logicDeletePropertyName("deleted")//逻辑删除属性名(实体)
                    .naming(NamingStrategy.underline_to_camel)//数据库表映射到实体的命名策略
                    .addTableFills(new Column("create_time", FieldFill.INSERT))//添加表字段填充
                    .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))//添加表字段填充
                    .idType(IdType.ASSIGN_ID)//全局主键类型
                    .formatFileName("%s")//格式化文件名称
                    .controllerBuilder()//Controller 策略配置
                    .enableHyphenStyle()//开启驼峰转连字符
                    .enableRestStyle()//开启生成@RestController 控制器
                    .formatFileName("%sController")//格式化文件名称
                    .serviceBuilder()//Service 策略配置
                    .formatServiceFileName("%sService")//格式化 service 接口文件名称
                    .formatServiceImplFileName("%sServiceImp")//格式化 service 实现类文件名称
                    .mapperBuilder()//Mapper 策略配置
                    .enableMapperAnnotation()//开启 @Mapper 注解
                    .formatMapperFileName("%sMapper")//格式化 mapper 文件名称
                    .formatXmlFileName("%sMapper")//格式化 xml 实现类文件名称
                    .build();
            })
            .execute();
    }
}

3、扩展

逻辑删除

说明:

只对自动注入的 sql 起效:

  • 插入: 不作限制
  • 查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
  • 更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
  • 删除: 转变为 更新

例如:

  • 删除: update user set deleted=1 where id = 1 and deleted=0
  • 查找: select id,name,deleted from user where deleted=0

字段类型支持说明:

  • 支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)
  • 如果数据库字段使用datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now()

附录:

  • 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
  • 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。

使用方法

步骤 1: 配置com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig

  • 例: application.yml
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

步骤 2: 实体类字段上加上@TableLogic注解

@TableLogic
private Integer deleted;

常见问题

1. 如何 insert ?

  1. 字段在数据库定义默认值(推荐)
  2. insert 前自己 set 值
  3. 使用 自动填充功能

2. 删除接口自动填充功能失效

  1. 使用 deleteById 方法(推荐)
  2. 使用 update 方法并: UpdateWrapper.set(column, value)(推荐)
  3. 使用 update 方法并: UpdateWrapper.setSql("column=value")
  4. 使用 Sql 注入器 注入 com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill 并使用(3.5.0版本已废弃,推荐使用deleteById)

通用枚举

解决了繁琐的配置,让 mybatis 优雅的使用枚举属性! 从 3.5.2 版本开始只需完成 步骤1: 声明通用枚举属性 即可使用

3.1.0开始,如果你无需使用原生枚举,可配置默认枚举来省略扫描通用枚举配置 默认枚举配置

  • 升级说明:

    3.1.0 以下版本改变了原生默认行为,升级时请将默认枚举设置为EnumOrdinalTypeHandler

  • 影响用户:

    实体中使用原生枚举

  • 其他说明:

    配置枚举包扫描的时候能提前注册使用注解枚举的缓存

步骤1: 声明通用枚举属性

方式一: 使用 @EnumValue 注解枚举属性

public enum GradeEnum {

    PRIMARY(1, "小学"),  SECONDORY(2, "中学"),  HIGH(3, "高中");

    GradeEnum(int code, String descp) {
        this.code = code;
        this.descp = descp;
    }

    @EnumValue//标记数据库存的值是code
    private final int code;
    //。。。
}

方式二: 枚举属性,实现 IEnum 接口如下:

public enum AgeEnum implements IEnum<Integer> {
    ONE(1, "一岁"),
    TWO(2, "二岁"),
    THREE(3, "三岁");

    private int value;
    private String desc;

    @Override
    public Integer getValue() {
        return this.value;
    }
}

实体属性使用枚举类型

public class User {
    /**
     * 名字
     * 数据库字段: name varchar(20)
     */
    private String name;

    /**
     * 年龄,IEnum接口的枚举处理
     * 数据库字段:age INT(3)
     */
    private AgeEnum age;


    /**
     * 年级,原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):
     * 数据库字段:grade INT(2)
     */
    private GradeEnum grade;
}

步骤2: 配置扫描通用枚举

  • 注意!! 从 3.5.2 开始无需配置
  • 注意!! spring mvc 配置参考,安装集成 MybatisSqlSessionFactoryBean 枚举包扫描,spring boot 例子配置如下:
方式一:仅配置指定包内的枚举类使用 MybatisEnumTypeHandler

配置文件 resources/application.yml

mybatis-plus:
    # 支持统配符 * 或者 ; 分割
    typeEnumsPackage: com.baomidou.springboot.entity.enums
  ....

当添加这个配置后,mybatis-plus 提供的 MybatisSqlSessionFactoryBean 会自动扫描包内合法的枚举类(使用了 @EnumValue 注解,或者实现了 IEnum 接口),分别为这些类注册使用 MybatisEnumTypeHandler

换句话说,只有指定包下的枚举类会使用新的 TypeHandler。其他包下,或者包内没有做相关改造的枚举类,仍然会使用 mybatis 的 DefaultEnumTypeHandler。

方式二:直接指定 DefaultEnumTypeHandler

此方式用来 全局 修改 mybatis 使用的 EnumTypeHandler。

配置文件 resources/application.yml

mybatis-plus:
    # 修改 mybatis 的 DefaultEnumTypeHandler
    configuration:
        default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler

自定义配置类 MybatisPlusAutoConfiguration

@Configuration
public class MybatisPlusAutoConfiguration {

    @Bean
    public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
        return properties -> {
            GlobalConfig globalConfig = properties.getGlobalConfig();
            globalConfig.setBanner(false);
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
            properties.setConfiguration(configuration);
        };
    }
}

如何序列化枚举值为前端返回值?

Jackson

一、重写 toString 方法
springboot
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer(){
    return builder -> builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
}
jackson
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);

以上两种方式任选其一,然后在枚举中复写 toString 方法即可.

二、注解处理
public enum GradeEnum {

    PRIMARY(1, "小学"),  SECONDORY(2, "中学"),  HIGH(3, "高中");

    GradeEnum(int code, String descp) {
        this.code = code;
        this.descp = descp;
    }

    @EnumValue
    @JsonValue	//标记响应json值
    private final int code;
}

Fastjson

一、重写 toString 方法
全局处理方式
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString);
局部处理方式
@JSONField(serialzeFeatures= SerializerFeature.WriteEnumUsingToString)
private UserStatus status;

以上两种方式任选其一,然后在枚举中复写 toString 方法即可.

字段类型处理器

类型处理器,用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值,本文讲解 mybatis-plus 内置常用类型处理器如何通过TableField注解快速注入到 mybatis 容器中。

  • JSON 字段类型
@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
    private Long id;

    ...


        /**
     * 注意!! 必须开启映射注解
     *
     * @TableName(autoResultMap = true)
     *
     * 以下两种类型处理器,二选一 也可以同时存在
     *
     * 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
     */
        @TableField(typeHandler = JacksonTypeHandler.class)
        // @TableField(typeHandler = FastjsonTypeHandler.class)
        private OtherInfo otherInfo;

}

该注解对应了 XML 中写法为

<result column="other_info" jdbcType="VARCHAR" property="otherInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />

自动填充功能

原理:

  • 实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
  • 注解填充字段 @TableField(.. fill = FieldFill.INSERT) 生成器策略部分也可以配置!
public class User {

    // 注意!这里需要标记为填充字段
    @TableField(.. fill = FieldFill.INSERT)
    private String fillField;

    ....
}
  • 自定义实现类 MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
        // 或者
        this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill ....");
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
        // 或者
        this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
        // 或者
        this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
    }
}

注意事项:

  • 填充原理是直接给entity的属性设置值!!!
  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
  • MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充
  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
  • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
  • 不需要根据任何来区分可以使用父类的fillStrategy方法
  • update(T t,Wrapper updateWrapper)时t不能为空,否则自动填充失效
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入填充字段
     */
    INSERT,
    /**
     * 更新填充字段
     */
    UPDATE,
    /**
     * 插入和更新填充字段
     */
    INSERT_UPDATE
}

SQL注入器

注入器配置

全局配置 sqlInjector 用于注入 ISqlInjector 接口的子类,实现自定义方法注入。

参考默认注入器 DefaultSqlInjector(opens new window)

  • SQL 自动注入器接口 ISqlInjector
public interface ISqlInjector {

    /**
     * <p>
     * 检查SQL是否注入(已经注入过不再注入)
     * </p>
     *
     * @param builderAssistant mapper 信息
     * @param mapperClass      mapper 接口的 class 对象
     */
    void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}

自定义自己的通用方法可以实现接口 ISqlInjector 也可以继承抽象类 AbstractSqlInjector 注入通用方法 SQL 语句 然后继承 BaseMapper 添加自定义方法,全局配置 sqlInjector 注入 MP 会自动将类所有方法注入到 mybatis 容器中。

执行SQL分析打印(性能分析工具)

该功能依赖 p6spy 组件,完美的输出打印 SQL 及执行时长 3.1.0 以上版本

示例工程:

  • p6spy 依赖引入

Maven:

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>最新版本</version>
</dependency>

Gradle:

compile group: 'p6spy', name: 'p6spy', version: '最新版本'
  • application.yml 配置:
spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:h2:mem:test
    ...
  • spy.properties 配置:
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

注意!

  • driver-class-name 为 p6spy 提供的驱动类
  • url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
  • 打印出 sql 为 null,在 excludecategories 增加 commit
  • 批量操作不打印 sql,去除 excludecategories 中的 batch
  • 批量操作打印重复的问题请使用 MybatisPlusLogFactory (3.2.1 新增)
  • 该插件有性能损耗,不建议生产环境使用。

数据安全保护

该功能为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露。

  • 3.3.2 开始支持
  • 配置安全

YML 配置:

// 加密配置 mpw: 开头紧接加密内容( 非数据库配置专用 YML 中其它配置也是可以使用的 )
spring:
  datasource:
    url: mpw:qRhvCwF4GOqjessEB3G+a5okP+uXXr96wcucn2Pev6Bf1oEMZ1gVpPPhdDmjQqoM
    password: mpw:Hzy5iliJbwDHhjLs1L0j6w==
    username: mpw:Xb+EgsyuYRXw7U7sBJjBpA==

密钥加密:

// 生成 16 位随机 AES 密钥
String randomKey = AES.generateRandomKey();

// 随机密钥加密
String result = AES.encrypt(data, randomKey);

如何使用:

// Jar 启动参数( idea 设置 Program arguments , 服务器可以设置为启动环境变量 )
--mpw.key=d1104d7c3b616f0b
  • 数据安全:

👉 字段加密解密(opens new window)

👉 字段脱敏(opens new window)

注意!

  • 加密配置必须以 mpw: 字符串开头
  • 随机密钥请负责人妥善保管,当然越少人知道越好。

多数据源

一个基于springboot的快速集成多数据源的启动器

这是一个第三方 mybatis 扩展库,与 mybatis-plus 本身无关,属于组织参与者小锅盖个人发起的项目,任何行为与 baomidou 组织其它成员无关。

简介

dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。

其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x

文档 | Documentation

详细文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611(opens new window)

特性

  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密 ENC()。
  • 支持每个数据库独立初始化表结构schema和数据库database。
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
  • 支持 自定义注解 ,需继承DS(3.2.0+)。
  • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
  • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
  • 提供 自定义数据源来源 方案(如全从数据库加载)。
  • 提供项目启动后 动态增加移除数据源 方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
  • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
  • 提供 **基于seata的分布式事务方案。
  • 提供 本地多数据源事务方案。

约定

  1. 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
  2. 配置文件所有以下划线 _ 分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
  3. 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
  4. 默认的数据源名称为 master ,你可以通过 spring.datasource.dynamic.primary 修改。
  5. 方法上的注解优先于类上注解。
  6. DS支持继承抽象类上的DS,暂不支持继承接口上的DS。

使用方法

  1. 引入dynamic-datasource-spring-boot-starter。
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>${version}</version>
</dependency>
  1. 配置数据源。
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver
       #......省略
       #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
# 多主多从                      纯粹多库(记得设置primary)                   混合配置
spring:                               spring:                               spring:
  datasource:                           datasource:                           datasource:
    dynamic:                              dynamic:                              dynamic:
      datasource:                           datasource:                           datasource:
        master_1:                             mysql:                                master:
        master_2:                             oracle:                               slave_1:
        slave_1:                              sqlserver:                            slave_2:
        slave_2:                              postgresql:                           oracle_1:
        slave_3:                              h2:                                   oracle_2:
  1. 使用 @DS 切换数据源。

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

注解 结果
没有@DS 默认数据源
@DS("dsName") dsName可以为组名也可以为具体某个库的名称
@Service
@DS("slave")
public class UserServiceImpl implements UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List selectAll() {
        return  jdbcTemplate.queryForList("select * from user");
    }

    @Override
    @DS("slave_1")
    public List selectByCondition() {
        return  jdbcTemplate.queryForList("select * from user where age >10");
    }
}

MybatisX快速开发插件

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

功能

XML 跳转

生成代码(需先在 idea 配置 Database 配置数据源)

重置模板

JPA 提示

生成新增

生成查询

生成修改

生成删除

常见问答

为什么 JPA 不能使用?
JPA 提示的方式需要根据 Mapper 找到实体类, 找到实体类有以下五种方式

  1. 继承 mybatis-plus 的 BaseMapper
  2. Mapper.xml 文件有 resultMap 标签
  3. 在 Mapper 类上增加注释指定实体类, 例如: @Entity com.xx.xx.UserModel

为什么生成的表名和期望的表名不一致
JPA 提示生成代码, 按照以下规则找到表名

  1. 实体类有 JPA 注解, 例如: @Table(name="t_user")
  2. 实体类有 mybais-plus 注解, 例如: @TableName("t_user")
  3. 实体类有注释: @TableName com.xx.xx.UserModel
  4. 如果不存在以上规则, 将驼峰转下划线. 例如 UserMode 的表名为: user_model

生成代码的模板配置

按照指定目录找到插件模板配置目录 Scratches and Consoles -> Extensions -> MybatisX
这里会提供默认模板: 例如在 1.4.13 提供了模板: default-all,default,mybatis-plus2,mybatis-plus3
如果想重置默认模板, 可以右键点击 MybatisX 目录,选择 Restore Default Extensions 选项

自定义模板内容

名称 含义
tableClass.fullClassName 类的全称(包括包名)
tableClass.shortClassName 类的简称
tableClass.tableName 表名
tableClass.pkFields 表的所有主键字段
tableClass.allFields 表的所有字段
tableClass.baseFields 排除主键和 blob 的所有字段
tableClass.baseBlobFields 排除主键的所有字段
tableClass.remark 表注释

字段信息

名称 含义
field.fieldName 字段名称
field.columnName 列名称
field.jdbcType jdbc 类型
field.columnLength 列段长度
field.columnScale 列的精度
field.columnIsArray 字段类型是不是数组类型
field.shortTypeName java 类型短名称, 通常用于定义字段
field.fullTypeName java 类型的长名称, 通常用于导入
field.remark 字段注释
field.autoIncrement 是否自增
field.nullable 是否允许为空

配置信息

名称 含义
baseInfo.shortClassName 配置名称
baseInfo.tableName 配置文件名称
baseInfo.pkFields 配置名称
baseInfo.allFields 后缀
baseInfo.baseFields 包名
baseInfo.baseBlobFields 模板内容
baseInfo.remark 相对模块的资源文件路径

企业高级特性

提示

mybatis-mate 为 mp 企业级模块,旨在更敏捷优雅处理数据。

  • mybatis-mate 示例 :传送门(opens new window)
  • 联系作者确认后微信公众号发文介绍 mybatis-mate 的软文,可免费获得永久个人授权证书
  • 该模块属于 mybatis (MP) 的扩展库,非 mp 的收费版本,任何问题由 青苗 个人负责。

👉 免费视频教程(opens new window)

数据审计(对账)

  • 对比两对象属性差异,例如:银行流水对账。
// 1,异步回调,注意 @EnableAsync 开启异步
applicationEventPublisher.publishEvent(new DataAuditEvent((t) -> {
    List<Change> changes = t.apply(newVersion, oldVersion);
    for (Change valueChange : changes) {
        ValueChange change = (ValueChange) valueChange;
        System.err.println(String.format("%s不匹配,期望值 %s 实际值 %s", change.getPropertyName(), change.getLeft(), change.getRight()));
    }
}));

// 2,手动调用对比
DataAuditor.compare(obj1, obj2);

数据敏感词过滤

  • 数据敏感词过滤(AC 算法)配置完处理器,框架自动处理请求的所有字符串敏感词过滤,支持嵌套关键词让敏感词无处遁形。
  • 数据库自维护敏感词库(免费、可控),默认加载缓存词根支持指定重新加载词库。

数据范围(数据权限)

  • 注解 @DataScope
属性 类型 必须指定 默认值 描述
type String "" 范围类型,用于区分对于业务分类,默认空
value DataColumn[] {} 数据权限字段,支持多字段组合
ignore boolean false 忽略权限处理逻辑 true 是 false 否
  • 注解 @DataColumn
属性 类型 必须指定 默认值 描述
alias String "" 关联表别名
name String 字段名
  • 行级粒度权限控制,例如:上级部门可以查看子部门信息。
// 测试 test 类型数据权限范围,混合分页模式
@DataScope(type = "test", value = {
    // 关联表 user 别名 u 指定部门字段权限
    @DataColumn(alias = "u", name = "department_id"),
    // 关联表 user 别名 u 指定手机号字段(自己判断处理)
    @DataColumn(alias = "u", name = "mobile")
})
@Select("select u.* from user u")
List<User> selectTestList(IPage<User> page, Long id, @Param("name") String username);

// 测试数据权限,最终执行 SQL 语句
SELECT u.* FROM user u WHERE (u.department_id IN ('1', '2', '3', '5')) AND u.mobile LIKE '%1533%' LIMIT 1,10

关于IDataScopeProvider的说明:

请注意必须注入 IDataScopeProvider 实现类处理数据权限,关于数据传参支持 2 种方式: 1,自定义 mapper 方法通过方法参数传递,在 setWhere 方法 Object[] args 参数中获取 2,利用 ThreadLocal 传递参数,你可以拦截 controller 层或者 service 层设置数据权限处理参数,更多可以 👉参考(opens new window)

表结构自动维护

  • 数据库 Schema 初始化,升级 SQL 自动维护,区别于 flyway 支持分表库、可控制代码执行 SQL 脚本
  • 首次会在数据库中生成 ddl_history 表,每次执行SQL脚本会自动维护版本信息。
@Component
public class MysqlDdl implements IDdl {

    /**
     * 执行 SQL 脚本方式
     */
    @Override
    public List<String> getSqlFiles() {
        return Arrays.asList(
            "db/tag-schema.sql",
            "D:\\db\\tag-data.sql"
        );
    }
}

// 切换到 mysql 从库,执行 SQL 脚本
ShardingKey.change("mysqlt2");
ddlScript.run(new StringReader("DELETE FROM user;\n" +
                               "INSERT INTO user (id, username, password, sex, email) VALUES\n" +
                               "(20, 'Duo', '123456', 0, 'Duo@baomidou.com');"));

字段数据绑定(字典回写)

  • 注解 @FieldBind
属性 类型 必须指定 默认值 描述
sharding String "" 分库分表数据源指定
type String 类型(用于区分不同业务)
target String 目标显示属性(待绑定属性,注意非数据库字段请排除)
  • 数据库 sex01 自动映射为
  • 可以绑定映射为对象,例如:根据订单 ID 映射 订单对象或者编号
@FieldBind(type = "user_sex", target = "sexText")
private Integer sex;
// 绑定显示属性,非表字典(排除)
@TableField(exist = false)
private String sexText;
  • 绑定业务处理类需要实现 IDataBind 接口,注入 spring 容器
@Component
public class DataBind implements IDataBind {
    ...
}

虚拟属性绑定

  • 注解 @JsonBind
@JsonBind("绑定类型")
public class User {
    ...
}
  • 返回 Json 虚拟属性绑定策略
@Component
public class JsonBindStrategy implements IJsonBindStrategy {

    @Override
    public Map<String, Function<Object, Map<String, Object>>> getStrategyFunctionMap() {
        return new HashMap<String, Function<Object, Map<String, Object>>>(16) {
            {
                // 注入虚拟节点
                put(Type.departmentRole, (obj) -> new HashMap(2) {{
                    User user = (User) obj;
                    // 枚举类型转换
                    put("statusText", StatusEnum.get(user.getStatus()).getDesc());
                    // 可调用数据库查询角色信息
                    put("roleName", "经理");
                }});
            }
        };
    }
}

字段加密解密

  • 注解 @FieldEncrypt
属性 类型 必须指定 默认值 描述
password String "" 加密密码
algorithm Algorithm PBEWithMD5AndDES PBE MD5 DES 混合算法
encryptor Class IEncryptor 加密处理器
  • 算法 Algorithm
算法 描述
MD5_32 32 位 md5 算法
MD5_16 16 位 md5 算法
BASE64 64 个字符来表示任意二进制数据算法
AES AES 对称算法
RSA 非对称加密算法
SM2 国密 SM2 非对称加密算法,基于 ECC
SM3 国密 SM3 消息摘要算法,可以用 MD5 作为对比理解
SM4 国密 SM4 对称加密算法,无线局域网标准的分组数据算法
PBEWithMD5AndDES 混合算法
PBEWithMD5AndTripleDES 混合算法
PBEWithHMACSHA512AndAES_256 混合算法
PBEWithSHA1AndDESede 混合算法
PBEWithSHA1AndRC2_40 混合算法

注意

MD5 算法为不可逆算法,存储数据库及查询结果都是密文 SM4 算法必须依赖 bouncycastle 加密库 混合算法必须依赖 jasypt 加密库 【注意】查询返回加密对象必须包含加密注解信息,单纯的返回某个 String 或者 List 某个集合是无法解密的。

  • 注解 FieldEncrypt 实现数据加解密,支持多种加密算法
@FieldEncrypt
private String email;

字段脱敏

  • 注解 @FieldSensitive
  • 注解 FieldSensitive 实现数据脱敏,内置 手机号邮箱银行卡号 等 9 种常用脱敏规则
@FieldSensitive("testStrategy")
private String username;

@Configuration
public class SensitiveStrategyConfig {

    /**
     * 注入脱敏策略
     */
    @Bean
    public ISensitiveStrategy sensitiveStrategy() {
        // 自定义 testStrategy 类型脱敏处理
        return new SensitiveStrategy().addStrategy("testStrategy", t -> t + "***test***");
    }
}

// 跳过脱密处理,用于编辑场景
RequestDataTransfer.skipSensitive();

多数据源分库分表(读写分离)

  • 注解 @Sharding
属性 类型 必须指定 默认值 描述
value String "" 分库组名,空使用默认主数据源
strategy Class RandomShardingStrategy 分库&分表策略
  • 配置
mybatis-mate:
  sharding:
    health: true # 健康检测
    primary: mysql # 默认选择数据源
    datasource:
      mysql: # 数据库组
        - key: node1
          ...
        - key: node2
          cluster: slave # 从库读写分离时候负责 sql 查询操作,主库 master 默认可以不写
          ...
      postgres:
        - key: node1 # 数据节点
          ...
  • 注解 Sharding 切换数据源,组内节点默认随机选择(查从写主)
@Mapper
@Sharding("mysql")
public interface UserMapper extends BaseMapper<User> {

    @Sharding("postgres")
    Long selectByUsername(String username);

}
  • 切换指定数据库节点
// 切换到 mysql 从库 node2 节点
ShardingKey.change("mysqlnode2");

多数据源动态加载卸载

👉 mybatis-mate-sharding-dynamic(opens new window)

多数据源事务( jta atomikos)

👉 mybatis-mate-sharding-jta-atomikos

4、插件

插件主体

注意

版本要求:3.4.0 版本以上

MybatisPlusInterceptor

该插件是核心插件,目前代理了 Executor#queryExecutor#updateStatementHandler#prepare 方法

属性

private List<InnerInterceptor> interceptors = new ArrayList<>();

InnerInterceptor

我们提供的插件都将基于此接口来实现功能

目前已有的功能:

  • 自动分页: PaginationInnerInterceptor
  • 多租户: TenantLineInnerInterceptor
  • 动态表名: DynamicTableNameInnerInterceptor
  • 乐观锁: OptimisticLockerInnerInterceptor
  • sql 性能规范: IllegalSQLInnerInterceptor
  • 防止全表更新与删除: BlockAttackInnerInterceptor

注意:

使用多个功能需要注意顺序关系,建议使用如下顺序

  • 多租户,动态表名
  • 分页,乐观锁
  • sql 性能规范,防止全表更新与删除

总结: 对 sql 进行单次改造的优先放入,不对 sql 进行改造的最后放入

使用方式(以分页插件举例)

spring

<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <!-- 其他属性 略 -->
    <property name="configuration" ref="configuration"/>
    <property name="plugins">
        <array>
            <ref bean="mybatisPlusInterceptor"/>
        </array>
    </property>
</bean>

<bean id="configuration" class="com.baomidou.mybatisplus.core.MybatisConfiguration">
    <!-- 需配置该值为false,避免1或2级缓存可能出现问题,该属性会在旧插件移除后一同移除 -->
    <property name="useDeprecatedExecutor" value="false"/>
</bean>

<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
    <property name="interceptors">
        <list>
            <ref bean="paginationInnerInterceptor"/>
        </list>
    </property>
</bean>

<bean id="paginationInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor">
    <!-- 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型 -->
    <constructor-arg name="dbType" value="H2"/>
</bean>

spring-boot

@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

mybatis-config.xml

<plugins>
    <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
        <property name="@page" value="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor"/>
        <property name="page:dbType" value="h2"/>
    </plugin>
</plugins>

property 的配置说明详见 MybatisPlusInterceptor#setProperties 的源码方法注释

拦截忽略注解 @InterceptorIgnore

属性名 类型 默认值 描述
tenantLine String "" 行级租户
dynamicTableName String "" 动态表名
blockAttack String "" 攻击 SQL 阻断解析器,防止全表更新与删除
illegalSql String "" 垃圾 SQL 拦截

该注解作用于 xxMapper.java 方法之上 各属性代表对应的插件 各属性不给值则默认为 false 设置为 true 忽略拦截 更多说明详见源码注释

分页插件

PaginationInnerInterceptor

支持的数据库

  • mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

属性介绍

属性名 类型 默认值 描述
overflow boolean false 溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法)
maxLimit Long 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法)
dbType DbType 数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法)
dialect IDialect 方言实现类(参见 插件#findIDialect 方法)

建议单一数据库类型的均设置 dbType

自定义的 mapper#method 使用分页

IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
// or (class MyPage extends Ipage<UserVo>{ private Integer state; })
MyPage selectPageVo(MyPage page);
// or
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
<select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">
    SELECT id,name FROM user WHERE state=#{state}
</select>

如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage; 如果想临时不分页,可以在初始化IPage时size参数传 <0 的值;
如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);
如果 xml 需要从 page 里取值,需要 page.属性 获取

其他:

生成 countSql 会在 left join 的表不参与 where 条件的情况下,把 left join 优化掉
所以建议任何带有 left join 的sql,都写标准sql,即给于表一个别名,字段也要 别名.字段

乐观锁插件

OptimisticLockerInnerInterceptor

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

乐观锁配置需要两步

1.配置插件

spring xml 方式:

<bean class="com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor" id="optimisticLockerInnerInterceptor"/>

<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
    <property name="interceptors">
        <list>
            <ref bean="optimisticLockerInnerInterceptor"/>
        </list>
    </property>
</bean>

spring boot 注解方式:

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

2.在实体类的字段上加上@Version注解

@Version
private Integer version;

说明:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!

示例:

// Spring Boot 方式
@Configuration
@MapperScan("按需修改")
public class MybatisPlusConfig {
    /**
     * 旧版
     */
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

    /**
     * 新版
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

多租户插件

TenantLineInnerInterceptor

属性介绍

属性名 类型 默认值 描述
tenantLineHandler TenantLineHandler 租户处理器( TenantId 行级 )
public interface TenantLineHandler {

    /**
     * 获取租户 ID 值表达式,只支持单个 ID 值
     * <p>
     *
     * @return 租户 ID 值表达式
     */
    Expression getTenantId();

    /**
     * 获取租户字段名
     * <p>
     * 默认字段名叫: tenant_id
     *
     * @return 租户字段名
     */
    default String getTenantIdColumn() {
        return "tenant_id";
    }

    /**
     * 根据表名判断是否忽略拼接多租户条件
     * <p>
     * 默认都要进行解析并拼接多租户条件
     *
     * @param tableName 表名
     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
     */
    default boolean ignoreTable(String tableName) {
        return false;
    }
}

说明:

多租户 != 权限过滤,不要乱用,租户之间是完全隔离的!!!
启用多租户后所有执行的method的sql都会进行处理.
自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是 inner join 的要写标准的 inner join)

防全表更新与删除插件


BlockAttackInnerInterceptor

针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除

注入MybatisPlusInterceptor类,并配置BlockAttackInnerInterceptor拦截器

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

测试示例(全表更新)

@SpringBootTest
public class QueryWrapperTest {

    @Autowired
    private UserService userService;

    /**
  + SQL:UPDATE user  SET name=?,email=?;
  */
    @Test
    public void test() {
        User user = new User();
        user.setId(999L);
        user.setName("custom_name");
        user.setEmail("xxx@mail.com");
        // com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
        userService.saveOrUpdate(user, null);
    }
}

测试示例(部分更新)

@SpringBootTest
public class QueryWrapperTest {

    @Autowired
    private UserService userService;

    /**
  + SQL:UPDATE user  SET name=?, email=? WHERE id = ?;
  */
    @Test
    public void test() {
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(User::getId, 1);
        User user = new User();
        user.setId(10L);
        user.setName("custom_name");
        user.setEmail("xxx@mail.com");
        userService.saveOrUpdate(user, wrapper);
    }}

动态表名插件

DynamicTableNameInnerInterceptor

注意事项:

  • 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换
  • 例如:真实表名为 user 设定为 mp_dt_user 处理器替换为 user_2019 等
posted @ 2022-09-06 08:27  Miana  阅读(623)  评论(0)    收藏  举报