Mybatis-Plus


简介

1. MyBatisPlus 介绍

MyBatis-Plus( 简称 MP),是一个 MyBatis 的增强工具包,只做增强不做改变. 为简化开

发工作、提高生产率而生

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

2. 代码及文档发布地址

官方地址:

http://mp.baomidou.com

代码发布地址:

Github: https://github.com/baomidou/mybatis-plus

Gitee: https://gitee.com/baomidou/mybatis-plus

文档发布地址:

http://mp.baomidou.com/#/?id=%E7%AE%80%E4%BB%8B

集成MP

1 . 创建数据库表

-- 创建库
CREATE DATABASE mp;
-- 使用库
USE mp;
-- 创建表
CREATE TABLE tbl_employee(
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 last_name VARCHAR(50),
 email VARCHAR(50),
 gender CHAR(1),
 age int
);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Tom','tom@atguigu.com',1,22);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Jerry','jerry@atguigu.com',0,25);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Black','black@atguigu.com',1,30);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('White','white@atguigu.com',0,35);

2. 添加依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>]

<properties>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
     <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

3. 引入数据源,配置数据源信息

通用CRUD

基于 Mybatis

  • 需要编写 EmployeeMapper 接口,并手动编写 CRUD 方法

  • 提供 EmployeeMapper.xml 映射文件,并手动编写每个方法对应的 SQL 语句.

基于 MP

  • 只需要创建 EmployeeMapper 接口, 并继承 BaseMapper 接口.这就是使用 MP

    (泛型指定的就是当前Mapper接口所操作的实体类)

  • 需要完成的所有操作,甚至不需要创建 SQL 映射文件。

生成id的策略

描述
AUTO 数据库ID自增
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert前自行set主键值
ASSIGN_ID 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)(它是Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。)
ASSIGN_UUID 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)(一串唯一随机36位字符串(32个字符串+4个“-”)的算法,十六进制)
ID_WORKER 分布式全局唯一ID 长整型类型(please use ASSIGN_ID)
UUID 32位UUID字符串(please use ASSIGN_UUID)
ID_WORKER_STR 分布式全局唯一ID 字符串类型(please use ASSIGN_ID)

雪花算法

snowflake的结构如下(每部分用-分开):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)

一共加起来刚好64位,为一个Long型。(转换成字符串后长度最多19)

snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。经测试snowflake每秒能够产生26万个ID。

/**
 * Twitter_Snowflake<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
 * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位,为一个Long型。<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
 */
public class SnowflakeIdWorker {

    // ==============================Fields===========================================
    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1420041600000L;

    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;

    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;

    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;

    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;

    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /** 工作机器ID(0~31) */
    private long workerId;

    /** 数据中心ID(0~31) */
    private long datacenterId;

    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;

    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    //==============================Constructors=====================================
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    // ==============================Methods==========================================
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        //上次生成ID的时间截
        lastTimestamp = timestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================
    /** 测试 */
    public static void main(String[] args) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
        for (int i = 0; i < 1000; i++) {
            long id = idWorker.nextId();
            System.out.println(Long.toBinaryString(id));
            System.out.println(id);
        }
    }
}

MyBatis-plus是默认开启驼峰命名转换的

@Data
@Component
@AllArgsConstructor
@NoArgsConstructor
//@TableName("employee")
public class Employee {
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    @TableField(value="last_name") //一般来说开启驼峰命名转换就可以了
    private String lastName;
    private String email;
    private Integer gender;
    private Integer age;
    @TableField(exist = false) //表名这个字段不在数据库表中
    private Integer salary;
}

生成id的策略也可以在配置文件中配置

mybatis-plus:
  type-aliases-package: top.codekiller.mybatispluslearn.pojo
  mapper-locations: classpath:/mapper/*.xml
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: assign_id
  #默认开启,不配也可以
  configuration:
    map-underscore-to-camel-case: true

获取主键自增主键的值

在原生mybatis中,需要使用

<insert id="insert" useGeneratedKeys="true" keyProperty="id"></insert>

而在MP中,可以直接获取

Integer key=employee.getId();

在MP中,insert支持插入空数据

 //Employee employee=new Employee(0,"MP","123@qq.com",1,16,5000);
Employee employee=new Employee(0,"MP","123@qq.com",1);
Integer result=employeeMapper.insert(employee);

#插入结果
+----+-----------+-------------------+--------+------+
| id | last_name | email             | gender | age  |
+----+-----------+-------------------+--------+------+
| 14 | MP        | 123@qq.com        | 1      | NULL |
+----+-----------+-------------------+--------+------+

更新数据

Employee employee=new Employee(6,"MP2","12345@qq.com",0,20);
Integer result=employeeMapper.updateById(employee);

查询数据(含简单分页查询)

//1. 通过id查询
Employee employee=employeeMapper.selectById(6);

//2. 通过多个列进行查询 id+lastName
Map<String,Object> map=new HashMap<>();
map.put("gender",1);
map.put("email","123@qq.com");
List<Employee> list =employeeMapper.selectByMap(map);
list.forEach(((value)-> System.out.println(value)));

//3. 通过多个id进行查询
List<Employee> list=employeeMapper.selectBatchIds(Arrays.asList(13,14));
list.forEach((value)-> System.out.println(value));

//4. 分页查询(需要配置插件,否则getRecords是获取全部数据。配置插件后通过limit进行查询)
Page<Employee> page =employeeMapper.selectPage(new Page<Employee>(2,2),null);
List<Employee> list=page.getRecords();
list.forEach((value)-> System.out.println(value));
@Configuration
@MapperScan("top.codekiller.mybatispluslearn.mapper")
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

删除数据

public int testCommonDelete(){
    //1. 通过id查询
    Integer result=employeeMapper.deleteById(11);

    //2.通过多个列删除
    Map<String,Object> map=new HashMap<>();
    map.put("id","12");
    map.put("last_name","MP");
    Integer result=employeeMapper.deleteByMap(map);

     //3. 通过多个id删除,我这里并不存在id为15的值,因此result=2
    Integer result=employeeMapper.deleteBatchIds(Arrays.asList(13,14,15));
    return result;
}

总结

以上是基本的 CRUD 操作,如您所见,我们仅仅需要继承一个 BaseMapper 即可实现大部分单表 CRUD 操作。BaseMapper 提供了多达 17 个方法给大家使用, 可以极其方便的实现单一、批量、分页等操作。极大的减少开发负担,难道这就是 MP 的强大之处了吗?

现有一个需求,我们需要分页查询 tbl_employee 表中,年龄在 18~50 之间性别为男且姓名为 xx 的所有用户,这时候我们该如何实现上述需求呢?

MyBatis : 需要在 SQL 映射文件中编写带条件查询的 SQL,并基于 PageHelper 插件完成分页. 实现以上一个简单的需求,往往需要我们做很多重复单调的工作。普通的 Mapper能够解决这类痛点吗?

MP: 依旧不用编写 SQL 语句, MP 提供了功能强大的条件构造器 EntityWrapper

##MP启动注入SQL原理分析

       问题: xxxMapper 继承了 BaseMapper, BaseMapper 中提供了通用的 CRUD 方法, 方法来源于 BaseMapper, 有方法就必须有 SQL, 因为 MyBatis 最终还是需要通过 SQL 语句操作数据.

Mybatis的源码分析

Debug

EmployeeMapper 的本质 com.baomidou.mybatisplus.core.override.MybatisMapperProxy

会创建一个MybatisMapperProxy,根据作者的注释,其实就是copy了一份MapperProxy的代码:joy:,因此和MapperProxy一样,使用的是JDK动态代理,此时会通过构造器传入一个SqlSession实例,和mapperInterface,methodCache。其中methodCache适用于存储method对象的,当调用一个method时,会通过cacheMapMethod方法在cache中进行查找,有则直接获取,无则创建一个method。

EmployeeMapperProxy中的的SqlSession是通过SqlSessionFactory创建的

每一个mapper配置文件中的SQL标签会对应于一个MappedStatement对象,此对象是在最初创建sqlSessionFactoryBuilder时,通过XMLConfigBuilder对mapper进行解析获得并放入Configuration对象中。

这里产生了一个问题,那就是MP到底是怎么构建sql语句的呢?

首先定位Delete类,该类注入了MappedStatement

public class Delete extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        //SqlMethod是一个枚举类,获取相应的SqlMethod对象
        //LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", 
        //"<script>\nUPDATE %s %s %s %s\n</script>"), 这个script标签标识的就是模板sql语句
        SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE;
        //是否开启逻辑删除
        if (tableInfo.isLogicDelete()) {
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),
                                sqlWhereEntityWrapper(true, tableInfo),
                                sqlComment());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            return addUpdateMappedStatement(mapperClass, modelClass, getMethod(sqlMethod),sqlSource);
        } else {
            //因为不是逻辑删除,所以要重新获取sqlMethod对象
            sqlMethod = SqlMethod.DELETE;
            //通过sql语句,表名,自定义wrapper,sql注释,获取最终的sql语句
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(),
                                sqlWhereEntityWrapper(true, tableInfo),
                                sqlComment());
            //创建一个sqlSource对象
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
            //增加MappedStatement,getMethod就是获取要注入的方法名
            return this.addDeleteMappedStatement(mapperClass, getMethod(sqlMethod), sqlSource);
        }
    }
}

mapperClass就是EmployeeMapper.class对象

modelClass:就是Employee.class对象

TableInfo:存储一些当前表的信息,比如:表名,字段名,实体类类型(Employee),idType,underCame(下划线驼峰)等

SqlMethod是一个枚举类,获取相应的SqlMethod对象。里面存放了SQL语句的模板

 LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", "<script>\nUPDATE %s %s %s %s\n</script>")
 DELETE("delete", "根据 entity 条件删除记录", "<script>\nDELETE FROM %s %s %s\n</script>"),
//这个script标签标识的就是模板sql语句

SqlSource:对sql语句进行处理

逻辑删除

/**
* 添加 MappedStatement 到 Mybatis 容器
*/
protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,
                                             SqlCommandType sqlCommandType, Class<?> parameterType,
                                             String resultMap, Class<?> resultType, KeyGenerator     
                                             keyGenerator,String keyProperty, String keyColumn) {

    String statementName = mapperClass.getName() + DOT + id;
    //判断是否该MappedStatement已经添加到容器中
    if (hasMappedStatement(statementName)) {
        logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);
        return null;
    }
    /* 缓存逻辑处理 */
    boolean isSelect = false;
    //也是一个枚举
    if (sqlCommandType == SqlCommandType.SELECT) {
        isSelect = true;
    }
    //增加mappedStatement
    return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,
                                               null, null, null, parameterType, resultMap, 
                                               resultType,null, !isSelect, isSelect, false, 
                                               keyGenerator, keyProperty, keyColumn,
                                                configuration.getDatabaseId(), languageDriver, null);                                      
}

MapperBuilderAssistant:创建MappedStatement对象,并且添加到configuration中

MapperBuilderAssistant

逻辑删除和物理删除

一:逻辑删除
逻辑删除的本质是修改操作,所谓的逻辑删除其实并不是真正的删除,而是在表中将对应的是否删除标识(is_delete)或者说是状态字段(status)做修改操作。比如0是未删除,1是删除。在逻辑上数据是被删除的,但数据本身依然存在库中。
对应的SQL语句:update 表名 set is_delete = 1 where id = 1;语句表示,在该表中将id为1的信息进行逻辑删除,那么客户端进行查询id为1的信息,服务器就不会提供信息。倘若想继续为客户端提供该信息,可将 is_delete 更改为 0 。

二:物理删除
物理删除就是真正的从数据库中做删除操作了。
对应的SQL语句:delete from 表名 where 条件;执行该语句,即为将数据库中该信息进行彻底删除,无法恢复。

关于回收站的原理,其实就是利用了逻辑删除,对于删除文件进入回收站的本质只是在操作系统的帮助下对文件加上了 某个标记,资源管理器中对含有这种标记的文件不会显示。当从回收站恢复的时候只是移除了加上的标记而已,而清空回收站就是进行了物理删除。
而商城网站,如淘宝,京东…会大量使用逻辑删除进行操作数据库。
切记,作为编程人员对于删除,一定要慎之又慎,一定要再三考虑。特别是物理删除,可以的话,就忘掉他吧。
!!!

配置文件配置

在pojo类中在逻辑删除的字段加注解@TableLogic,当这个字段值为0时,说明数据未被删除,提供给用户正常使用,当这个字段值为1是,说明数据已被删除,用户无法正常查到这条数据;配置及注解内容如下图。可以配置select=false,表示在select语句中,该字段不会被查询到。(数据库表中也要插入deleted字段才行

mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 0
      logic-not-delete-value: 1

实体类相应字段配置

@TableLogic
@TableField(select = false)
private Integer deleted;

条件构造器

条件查询(QueryWrapper)

public Employee testQueryWrapperSelect(){
    //我们需要分页查询tbl_employee表中,年龄在16-50之间性别为男性姓名为MP的所有用户
    Page<Employee> page=employeeMapper.selectPage(new Page<Employee>(1,3),
                                                  new QueryWrapper<Employee>().
                                                  between("age",16,50).
                                                  eq("gender",1).
                                                  eq("last_name","MP"));
    List<Employee> emps=page.getRecords();
    emps.forEach((value)-> System.out.println(value));

     //另一种分页,获取每行存在list集合中,并且每行的字段都放在map集合中。key:value=columnName:value
    Page<Map<String,Object>> page=employeeMapper.selectMapsPage(new Page<Map<String,Object>>(1,3),
                                                                new QueryWrapper<Employee>().lambda()
                                                                .between(Employee::getAge,15,50)
                                                                .eq(Employee::getGender,1)
                                                                .eq(Employee::getLastName,"MP"));
    List<Map<String,Object>> emps=page.getRecords();
    for(Map<String,Object> map:emps){
        for(String key:map.keySet()){
            System.out.print(key+"--->"+map.get(key)+"; ");
        }
        System.out.println();
    }

    //使用last。查询为女的,根据age进行排序(asc/desc),进行分页
    List<Employee> emps=employeeMapper.selectList(new QueryWrapper<Employee>().lambda()
                                                  .eq(Employee::getGender,0)
                                                  .orderByDesc(Employee::getId)
                                                  .last("limit 1,3"));
    emps.forEach((value)-> System.out.println(value));

     //查询表中,性别为女并且名字中带有“老师”或者邮箱中开头是1的并且年龄小于19并且按age进行升序排序
    //lambda方式LambdaQueryWrapper
    List<Employee> emps=employeeMapper.selectList(new QueryWrapper<Employee>().lambda()
                                                  .eq(Employee::getGender,"0")
                                                  .and((i)->i.like(Employee::getLastName,"老师")
                                                       .or().likeRight(Employee::getEmail,"1"))
                                                  .orderByAsc(Employee::getAge)
                                                  .lt(Employee::getAge,19));
    emps.forEach((value)-> System.out.println(value))}

条件修改(UpdateWrapper)

public int testUpdateWrapperUpdate(){
    //用lambda设置where条件和少量set
    Employee employee=new Employee();
    employee.setLastName("苍什么空");
    employee.setGender(0);
    int result=employeeMapper.update(employee,new UpdateWrapper<Employee>().lambda()
                                     .eq(Employee::getLastName,"MP")
                                     .eq(Employee::getGender,1)
                                     .set(Employee::getEmail,"123qwe@qq.com"));

    //直接借用两个Employee实现修改和条件判断
    Employee employee1=new Employee();
    employee1.setLastName("MP");
    employee1.setGender(1);
    employee1.setEmail("123qwe@qq.com");
    int result=employeeMapper.update(employee,new UpdateWrapper<Employee>(employee1));

    return result;
}

条件删除(UpdateWrapper)

inSql

inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
  • 字段 IN ( sql语句 )
  • 例: inSql("age", "1,2,3,4,5,6")—>age in (1,2,3,4,5,6)
  • 例: inSql("id", "select id from table where id < 3")—>id in (select id from table where id < 3)

notInSql

notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
  • 字段 NOT IN ( sql语句 )
  • 例: notInSql("age", "1,2,3,4,5,6")—>age not in (1,2,3,4,5,6)
  • 例: notInSql("id", "select id from table where id < 3")—>age not in (select id from table where id < 3)

exists

exists(String existsSql)
exists(boolean condition, String existsSql)
  • 拼接 EXISTS ( sql语句 )
  • 例: exists("select id from table where age = 1")—>exists (select id from table where age = 1)

notExists

notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
  • 拼接 NOT EXISTS ( sql语句 )
  • 例: notExists("select id from table where age = 1")—>not exists (select id from table where age = 1)

having

having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
  • HAVING ( sql语句 )
  • 例: having("sum(age) > 10")—>having sum(age) > 10
  • 例: having("sum(age) > {0}", 11)—>having sum(age) > 11

apply

apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
  • 拼接 sql

注意事项:

该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有!

  • 例: apply("id = 1")—>id = 1
  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")—>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")—>date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")

last

last(String lastSql)
last(boolean condition, String lastSql)
  • 无视优化规则直接拼接到 sql 的最后

注意事项:

只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用

  • 例: last("limit 1")

AR

介绍

Active Record(活动记录),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录

ActiveRecord 一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索。

AR 模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 Mybatis 对应的方法,类似于语法糖

语法糖是指计算机语言中添加的某种语法,这种语法对原本语言的功能并没有影响。可以更方便开发者使用,可以避免出错的机会,让程序可读性更好.

继承Model<T extends Model<?>>

@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Employee extends Model<Employee2> {

    @Override
    protected Serializable pkVal() {
        return id;
 }

AR查询

public Employee2 testARSelect(){
    Employee2 employee=new Employee2();

    //通过id查询
    employee.setId(14);
    Employee2 result=employee.selectById();

    //通过直接传参id查询
    employee.selectById(14);

    //查询所有数据
    List<Employee2> emps=employee.selectAll();
    emps.forEach((value)-> System.out.println(emps));

    //查询符合条件的数据
    List<Employee2> emps=employee.selectList(new QueryWrapper<Employee2>().lambda()
                                             .like(Employee2::getLastName,"老师"));
    emps.forEach((value)-> System.out.println(value));

    //查询符合条件的数据的数目
    int cout=employee.selectCount(new QueryWrapper<Employee2>().lambda()
                                  .eq(Employee2::getGender,0));
    System.out.println(cout);

    return employee;
}

AR分页查询

public List<Employee2> testARSelectPage(){
    Employee2 employee=new Employee2();
    Page<Employee2> page=employee.selectPage(new Page<Employee2>(1,2),new QueryWrapper<Employee2>().lambda()
                                             .like(Employee2::getLastName,"老"));
    List<Employee2> emps=page.getRecords();
    emps.forEach((value)-> System.out.println(value));
    return null;
}

AR删除

public Boolean testARDelete(){
    Employee2 employee=new Employee2();

    employee.setId(14);
    boolean result=employee.deleteById();

    boolean result=employee.deleteById(14);

    boolean result=employee.delete(new UpdateWrapper<Employee2>().lambda()
                                   .like(Employee2::getLastName,"小"));

    return result;
}

AR插入

public boolean testARInsert(){
    Employee2 employee=new Employee2();
    employee.setLastName("波什么衣");
    employee.setEmail("LLL888@gmail.com");
    employee.setGender(0);
    employee.setAge(30);
    boolean result=employee.insert();
    return result;
}

AR更新

public boolean testARUpdate(){
    Employee2 employee2=new Employee2(14,"波什么衣","LLL888@gmail.com",0,31);
    boolean result=employee2.updateById();
    return result;
}

代码生成器

简介

     MP 提供了大量的自定义设置,生成的代码完全能够满足各类型的需求。AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

MP 的代码生成器 和 Mybatis MBG 代码生成器的区别

  • MP 的代码生成器都是基于 java 代码来生成。MBG 基于 xml 文件进行代码生成

  • MyBatis 的代码生成器可生成: 实体类、Mapper 接口、Mapper 映射文件

       MP 的代码生成器可生成: 实体类(可以选择是否支持 AR)、Mapper 接口、Mapper 映射文件、 Service 层、Controller        层.

表及字段命名策略选择

      在 MP 中,我们建议数据库表名 和 表字段名采用驼峰命名方式, 如果采用下划线命名方式 请开启全局下划线开关,如果表名字段名命名方式不一致请注解指定,我们建议最好保持一致。

这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虑这点性能损耗,那么你采用下滑线也是没问题的,只需要在生成代码时配置map-underscore-to-camel-case属性就可以。

添加依赖

添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)、FreemarkerBeetl,用户可以选择自己熟悉的模板引擎,如果都不满足要求,可以采用自定义模板引擎。

<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity</artifactId>
    <version>1.7</version>
</dependency>

生成器代码

public void codeGenerator(){
        //代码生成器
        AutoGenerator autoGenerator=new AutoGenerator();

        //全局配置
        GlobalConfig globalConfig=new GlobalConfig();
        globalConfig.setActiveRecord(false) //是否支持AR模式
                    .setAuthor("codekiller") //设置作者
                   .setOutputDir("D:\\workspace\\MybatisPlusGenerator\\mybatispluslearngenerator\\src\\main\\java")//设置生成路径,和父包名连用
                    .setFileOverride(true)  //是否文件覆盖
                    .setIdType(IdType.AUTO)  //设置主键策略
                    .setServiceName("%sService")  //设置生成的service接口的名字的首字母是否为I,eg:IEmployeeService
                    .setBaseResultMap(true)
                    .setBaseColumnList(true)
                    .setOpen(false);
        autoGenerator.setGlobalConfig(globalConfig);

        //数据源配置
        DataSourceConfig dataSourceConfig=new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mp?characterEncoding=UTF-8&serverTimezone=UTC");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("root");
        autoGenerator.setDataSource(dataSourceConfig);

        //策略配置
        StrategyConfig strategyConfig=new StrategyConfig();
        strategyConfig.setCapitalMode(true)    //全局大写命名
                      .setTablePrefix("tbl_")
//                      .setInclude("tbl_employee")//指定生成的表
                      .setNaming(NamingStrategy.underline_to_camel)
                      .setColumnNaming(NamingStrategy.underline_to_camel)
                      .setEntityLombokModel(true)
                      .setControllerMappingHyphenStyle(true);
        autoGenerator.setStrategy(strategyConfig);

        //包策略配置
        PackageConfig packageConfig=new PackageConfig();
        packageConfig.setParent("top.codekiller.mybatisplus.mybatispluslearngenerator")
//                    .setModuleName() //设置模块名
                      ;
        autoGenerator.setPackageInfo(packageConfig);

        //模板配置
        TemplateConfig templateConfig=new TemplateConfig();
        //可以不在mapper目录下生成xml文件,与InjectionConfig连用可以在指定目录下生成xml文件
        templateConfig.setXml(null);
        autoGenerator.setTemplate(templateConfig);


        //自定义输出配置
        InjectionConfig injectionConfig=new InjectionConfig() {
            @Override
            public void initMap() {

            }
        };


        // 如果模板引擎是 freemarker
//        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        String templatePath = "/templates/mapper.xml.vm";

        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return "D:\\workspace\\MybatisPlusGenerator\\mybatispluslearngenerator\\src\\main\\resources\\mapper\\"
                        +tableInfo.getEntityName()+"Mapper.xml";
            }
        });
        injectionConfig.setFileOutConfigList(focList);
        autoGenerator.setCfg(injectionConfig);

        autoGenerator.execute();

    }

Service层的注意事项

Service层的接口自动继承了了IService接口,里面有一些常用操作的接口

Service层的实现类实现了Service层的接口,并且继承了ServiceImpl类,该类实现了IService接口。所以我们的serviceimpl不需要写一些简单具体操作的实现。并且也不需要自动注入mapper,在ServiceImpl中已经帮我们注入了;

//自己的service接口
public interface EmployeeService extends IService<Employee> {

}

//自己的serviceimpl
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeServiec {

}

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    protected M baseMapper;
    .........
}

Plugin

分页插件(相关实例)

@Configuration
@MapperScan("top.codekiller.mybatisplus.mybatispluslearngenerator.mapper")
@EnableTransactionManagement
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

执行 SQL 分析打印

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

引入p6spy依赖

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.9.0</version>
</dependency>

修改数据源信息

url: jdbc:p6spy:mysql://localhost:3306/mp?characterEncoding=UTF-8&serverTimezone=UTC
driver-class-name: com.p6spy.engine.spy.P6SpyDriver

Druid报错

如果使用Druid数据源,一定要记得配置一下filter,不然WallFilter和StatFilter初始化时找不到匹配的dbType抛出异常。

filters:
  config:
    enabled: true
  stat:
    enabled: true
    db-type: mysql
  wall:
      enabled: true
    db-type: mysql
  slf4j:
    enabled: true
    db-type: mysql

或者直接不要

druid:
  filters: config

配置spy.properties

modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,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新增)
  • 该插件有性能损耗,不建议生产环境使用

性能分析插件

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

该插件 3.2.0 以上版本移除推荐使用第三方扩展 执行SQL分析打印 功能

乐观锁插件

数据库表中必须有verson字段

在容器中添加插件

@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
    return new OptimisticLockerInterceptor();
}

注解实体字段 @Version 必须要!

@Version
private Integer version;

测试数据

@Test
void contextLoads() {
    Employee employee=new Employee();
    employee.setId(4);
    employee.setEmail("13579@gmail.com");
    int verson=employeeServiec.getById(4).getVersion();
    employee.setVersion(verson);
    boolean result=employeeServiec.updateById(employee);
    System.out.println(result);
}

特别说明:

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

sql注入器

1. 把方法定义到BaseMapper,参考MyBaseMapper

public interface MyBaseMapper<T> extends BaseMapper<T> {

    int deleteByName(@Param("name") String name);

}

2. 定义SQL:MysqlInsertAllBatch. 参考Delete

class DeleteByName extends AbstractMethod{
        @Override
        public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
            //注入自定义sql
            String sql="delete from"+tableInfo.getTableName()+"where name="+1;
            //注入的方法名
            String methodName="deleteByName";
            //可以判断是否是逻辑删除,这里不做测试了
            SqlSource sqlSource=languageDriver.createSqlSource(configuration,sql,modelClass);
            return this.addDeleteMappedStatement(mapperClass,methodName,sqlSource);
        }
    }

3. 注册: 注册自定义方法.参考MyLogicSqlInjector。

 class MySqlInjector extends DefaultSqlInjector{
        @Override
        public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
            //保留原有方法
            List<AbstractMethod> list=super.getMethodList(mapperClass);
            //新增方法
            list.add(new DeleteByName());
            return list;
        }
    }

注意: 自定义全局方法里的id一定要和baseMapper的方法名称一致

坑点

  • 在演示自定义批量和自动填充功能时,需要在mapper方法的参数上定义@Param(),
  • 而mp默认仅支持list, collection, array3个命名,不然无法自动填充

逻辑删除

也可参考

在yml中进行配置

mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1 #逻辑已删除值(默认为 1)
      logic-not-delete-value: 0  # 逻辑未删除值(默认为 0)
      logic-delete-field: deleteFlag #全局逻辑删除字段值

也可用注解配置

@TableLogic(/*value = "0",delval = "1"*/) //如果没有表明value和delval会从去全局配置文件中查找
private Integer deleteFlag;

如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。

使用此配置则不需要在实体类上添加 @TableLogic。

但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。 即先查找注解再查找全局,都没有则此表没有逻辑删除。

真正执行的sql语句

 Execute SQL:UPDATE tbl_employee SET delete_flag=1 WHERE id=1 AND delete_flag=0

附件说明

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

如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。

  • 若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。

自动填充

metaobject: 元对象. 是 Mybatis 提供的一个用于更加方便,更加优雅的访问对象的属性,

给对象的属性设置值 的一个对象. 还会用于包装对象. 支持对 Object 、Map、Collection

等对象进行包装

本质上 metaObject 获取对象的属性值或者是给对象的属性设置值,最终是要

通过 Reflector 获取到属性的对应方法的 Invoker, 最终

在容器中导入MetaObjectHandler组件

@Slf4j
@Configuration
@MapperScan("top.codekiller.mybatisplus.mybatispluslearngenerator.mapper")
@EnableTransactionManagement
public class MybatisPlusConfig {
    @Bean
    public MyMetaObjectHandler myMetaObjectHandler(){
        return new MyMetaObjectHandler();
    }
    //...分页插件 乐观锁插件 sql注入器...

    //自动填充

    class MyMetaObjectHandler implements MetaObjectHandler{
          /**
         * 插入填充
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            //获取到需要被填充的字段的值
            Object filedValue=getFieldValByName("lastName",metaObject);
            if(filedValue==null){
                log.info("***********插入操作 满足填充条件************");
                setFieldValByName("lastName","泷什么拉",metaObject);
            }
-----------------------------------上述操作已过时,推荐使用下面的----------------------------------------
            strictInsertFill(metaObject,"lastName",String.class, "泷什么拉");
            strictInsertFill(metaObject,"regisDate", LocalDateTime.class, LocalDateTime.now());
        }

        /**
         * 修改填充
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            //获取到需要被填充的字段的值
            Object filedValue=getFieldValByName("lastName",metaObject);
            if(filedValue==null){
                log.info("***********修改操作 满足填充条件************");
                setFieldValByName("lastName","泷什么拉",metaObject);
            }
-----------------------------------上述操作已过时,推荐使用下面的----------------------------------------
            strictInsertFill(metaObject,"lastName",String.class, "泷什么拉");
            strictInsertFill(metaObject,"regisDate", LocalDateTime.class, LocalDateTime.now());
        }
    }
} 

配置注解

@TableField(fill = FieldFill.INSERT_UPDATE)  //配置字段填充策略
private String lastName; 
public enum FieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入填充字段
     */
    INSERT,
    /**
     * 更新填充字段
     */
    UPDATE,
    /**
     * 插入和更新填充字段
     */
    INSERT_UPDATE
}

注意事项:

  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
  • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
  • 不需要根据任何来区分可以使用父类的fillStrategy方法

测试

/**
* 测试自动填充
*/
@Test
void testMetaObjectHandler(){
    Employee employee=new Employee();
    employee.setVersion(1);
    employee.setDeleteFlag(0);
    boolean result=employeeServiec.save(employee);
    System.out.println(result);
}

结果

2020-04-08 18:15:38.230 DEBUG 15540 --- [           main] t.c.m.m.config.MybatisPlusConfig         : ***********插入操作 满足填充条件************

 Execute SQL:INSERT INTO tbl_employee ( last_name, regis_date, version, delete_flag ) VALUES ( '泷什么拉', '2020-04-08T18:51:30.959', 1, 0 )

序列Sequence

主键生成策略必须使用INPUT

支持父类定义@KeySequence子类继承使用

支持主键类型指定(3.3.0开始自动识别主键类型)

内置支持:

  • DB2KeyGenerator
  • H2KeyGenerator
  • KingbaseKeyGenerator
  • OracleKeyGenerator
  • PostgreKeyGenerator

如果内置支持不满足你的需求,可实现IKeyGenerator接口来进行扩展.

配置keyGenerator

@Bean
public IKeyGenerator keyGenerator() {
    return new H2KeyGenerator();
}

注释

@KeySequence(value = "SEQ_ORACLE_STRING_KEY"/*, clazz = String.class*/) //value是序列的名字;clazz是主键的类型,3.1.2版本后无需指定,自动进行匹配。
public class YourEntity {

    @TableId(value = "ID_STR", type = IdType.INPUT)
    private String idStr;

}

MybatisX插件

相关Maven依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--jdbc启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--mysql相关依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>
        <!--MP依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1</version>
        </dependency>
        <!--导入lombok依赖,记得配插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--导入代码生成器依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.3.1</version>
        </dependency>
        <!--导入代码生成器模板依赖-->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>
        <!--导入执行SQL分析打印的依赖-->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.9.0</version>
        </dependency>
    </dependencies>

yml配置

spring:
  datasource:
    username: root
    password: root
    url: jdbc:p6spy:mysql://localhost:3306/mp?characterEncoding=UTF-8&serverTimezone=UTC
    # 使用了p6spy进行sql分析打印(数据源的filter也要进行修改)
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver 
    type: com.alibaba.druid.pool.DruidDataSource
    # 下面为连接池的补充设置,应用到上面所有数据源中
    # 初始化大小,最小,最大
    initialSize: 5   #初始化创建的连接数
    minIdle: 5  #最小空闲连接数
    maxActive: 20 #最大活跃连接数,不宜设置过多
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 30000
    #用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。
    validationQuery: select 'x';
    #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,
    testWhileIdle: true
    #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    testOnBorrow: false
    #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
    poolPreparedStatements: true
    #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
    #在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
    maxPoolPreparedStatementPerConnectionSize: 20
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,stat用于监控统计,'wall'用于防火墙,防御sql注入,slf4j用于日志
    filters:
      config:
        enabled: true
      stat:
        enabled: true
        db-type: mysql
      wall:
        enabled: true
        db-type: mysql
      slf4j:
        enabled: true
        db-type: mysql

    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    # 合并多个DruidDataSource的监控数据
    useGlobalDataSourceStat: true




mybatis-plus:
  type-aliases-package: top.codekiller.mybatisplus.mybatispluslearngenerator.entity
  mapper-locations: classpath:/mapper/*Mapper.xml
  global-config:
    db-config:
      table-prefix: tbl_
      id-type: assign_id
      logic-delete-value: 0
      logic-not-delete-value: 1
      logic-delete-field: deleteFlag


logging:
  level:
    top.codekiller.mybatisplus.mybatispluslearngenerator: debug
  file:
    name: F:/MPtest.log

代码生成器

package top.codekiller.mybatisplus.mybatispluslearngenerator.config;


import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;

/**
 * 代码生成
 */
@Component
public class CodeGenerator {


    public void codeGenerator(){
        //代码生成器
        AutoGenerator autoGenerator=new AutoGenerator();

        //全局配置
        GlobalConfig globalConfig=new GlobalConfig();
        globalConfig.setActiveRecord(false) //是否支持AR模式
                    .setAuthor("codekiller") //设置作者
                    .setOutputDir("D:\\workspace\\MybatisPlusGenerator\\mybatispluslearngenerator\\src\\main\\java")//设置生成路径,和父包名连用
                    .setFileOverride(true)  //是否文件覆盖
                    .setIdType(IdType.AUTO)  //设置主键策略
                    .setServiceName("%sService")  //设置生成的service接口的名字的首字母是否为I,eg:IEmployeeService
                    .setBaseResultMap(true)
                    .setBaseColumnList(true)
                    .setOpen(false);
        autoGenerator.setGlobalConfig(globalConfig);

        //数据源配置
        DataSourceConfig dataSourceConfig=new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mp?characterEncoding=UTF-8&serverTimezone=UTC");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("root");
        autoGenerator.setDataSource(dataSourceConfig);

        //策略配置
        StrategyConfig strategyConfig=new StrategyConfig();
        strategyConfig.setCapitalMode(true)    //全局大写命名
                      .setTablePrefix("tbl_")
//                      .setInclude("tbl_employee")//指定生成的表
                      .setNaming(NamingStrategy.underline_to_camel)
                      .setColumnNaming(NamingStrategy.underline_to_camel)
                      .setEntityLombokModel(true)
                      .setControllerMappingHyphenStyle(true);
        autoGenerator.setStrategy(strategyConfig);

        //包策略配置
        PackageConfig packageConfig=new PackageConfig();
        packageConfig.setParent("top.codekiller.mybatisplus.mybatispluslearngenerator")
//                    .setModuleName() //设置模块名
                      ;
        autoGenerator.setPackageInfo(packageConfig);

        //模板配置
        TemplateConfig templateConfig=new TemplateConfig();
        //可以不在mapper目录下生成xml文件,与InjectionConfig连用可以在指定目录下生成xml文件
        templateConfig.setXml(null);
        autoGenerator.setTemplate(templateConfig);


        //自定义输出配置
        InjectionConfig injectionConfig=new InjectionConfig() {
            @Override
            public void initMap() {

            }
        };


        // 如果模板引擎是 freemarker
//        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        String templatePath = "/templates/mapper.xml.vm";

        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return "D:\\workspace\\MybatisPlusGenerator\\mybatispluslearngenerator\\src\\main\\resources\\mapper\\"
                        +tableInfo.getEntityName()+"Mapper.xml";
            }
        });
        injectionConfig.setFileOutConfigList(focList);
        autoGenerator.setCfg(injectionConfig);

        autoGenerator.execute();

    }

}

文章作者: 迷雾总会解
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 迷雾总会解 !
评论
  目录