随笔 - 24  文章 - 0  评论 - 1  阅读 - 1188 

跟随尚硅谷MyBatisPlus教程整理

MyBatisPlus

1、简介

MyBatis的增强工具包(简称:MP),只做增强,不做改变,为简化工作、提高生产效率而生

官方地址:

http://mp.baomidou.com/

代码发布地址:

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

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

MyBatis-Plus Samples 工程案例:https://gitee.com/baomidou/mybatis-plus-samples/tree/master/

前置知识:

Mybatis

Spring

Maven

特点:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

2、集成MP

2.1、创建测试表

数据库创建

复制代码
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)
 );
 ​
 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');
复制代码

插入数据,创建这样一个表

idnameageemail
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

2.2、创建JavaBean

与表中的字段对应,设置对应的getter、setter方法

定义JavaBean中成员变量时所使用的类型:

基本类型使用包装类型,因为每个基本类型都有一个默认值,int的为0,boolean为false,无法完成空值判断(NULL)

int Integer 对应 int

long Long 对应 bigint

boolean Boolean 可使用 tinyint 的 0 或 1 表示 true 或 false

char Character

String 对应 varchar 或 char

Date 和 LocalDateTime 对应 date 和 datetime

……

2.3、引入依赖

版本可更改

复制代码
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter</artifactId>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
 </dependency>
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>Latest Version</version>
 </dependency>
复制代码

注:Mybatis和Mybatis-Spring依赖不加入配置,以免引起版本冲突,mp自动进行维护

2.4、配置

application.yml

 # DataSource Config
 spring:
  datasource:
    driver-class-name: # 驱动类
    url: #
    username: #
    password: #

2.5、编写Mapper类

继承BaseMapper,泛型加入之前写的实体类

 public interface UserMapper extends BaseMapper<User> {
 
 }

2.6、启动类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹(Mapper对应路径):

 @SpringBootApplication
 @MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
 public class Application {
 
     public static void main(String[] args) {
         SpringApplication.run(Application.class, args);
    }
 
 }

在测试类中可以直接调用Mapper接口实现CRUD操作,完成!

3、通用CRUD

3.1、与MyBatis对比

基于MyBatis实现:完成mybatis-config.xml全局配置,编写Mapper接口,手动编写CRUD方法,提供mapper.xml映射文件,并手动编写每个方法对应的SQL语句,按需配置输出映射resultMap等。

基于MP实现:创建Mapper接口,并继承BaseMapper接口,使用MP完成所有操作,甚至不需要创建SQL映射文件

优缺点:MP针对单表有较强的优化简便操作,但面对多表联合等仍然需要进行手动编写SQL语句,沿用MyBatis的操作加入映射文件

3.2、实体类修饰

与数据库中的表信息对应

建议属性满足驼峰命名,字段满足下划线命名,自动完成匹配(全局策略)dbColumnUnderline = true 默认

可使用一些注解修饰,实现信息对应

1.表名注解@TableName(value="{*}")

2.主键注解@TableId(value="{id}",type=IdType.{*})

主键叫id可无注解

设置type=IdType.Auto实现自增

IdType.ASSIGN_ID分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
IdType.ASSIGN_UUID 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)

3.字段注解@TableField(value = "{*}") 非主键注解

exist = false 该属性是否在数据库字段中存在,常用于对象不需要经过数据库的属性,但仍然需要getter、setter方法

3.3、insert插入

 // 插入一条记录
 int insert(T entity);

创建一个新的实体对象object,设置相应属性

使用mapper.insert(object),后续都是调用basemapper的方法

在插入时,根据实体类的每个属性进行非空判断,只有非空的字段才会出现到SQL语句中

3.4、update修改

 // 根据 whereWrapper 条件,更新记录
 int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
 // 根据 ID 修改
 int updateById(@Param(Constants.ENTITY) T entity);

updateById可通过创建新的实体对象并设置属性后作为参数传入

3.5、select查询

复制代码
 // 根据 ID 查询
 T selectById(Serializable id);
 // 根据 entity 条件,查询一条记录
 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
 ​
 // 查询(根据ID 批量查询)
 List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
 // 根据 entity 条件,查询全部记录
 List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
 // 查询(根据 columnMap 条件)
 List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
 // 根据 Wrapper 条件,查询全部记录
 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
 // 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
 ​
 // 根据 entity 条件,查询全部记录(并翻页)
 IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
 // 根据 Wrapper 条件,查询全部记录(并翻页)
 IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);.
 // 根据 Wrapper 条件,查询总记录数
 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
复制代码

selectById 根据主键id查询

selectOne 创建一个实体对象,设置代表查询条件的属性,作为参数进行查询,只查询一条记录,如果出现多个结果则抛出TooManyResultException

selectBatchIds 批量查询 可以加入一个Collection,如ArrayList,插入批量的id

selectByMap 通过Map封装查询条件 key: 数据库字段名 —— value: 查询条件

selectPage 简单分页,Page设置new Page(a,b),表示查第a页,每页b条。

3.6、delete删除

 // 根据 entity 条件,删除记录
 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
 // 删除(根据ID 批量删除)
 int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
 // 根据 ID 删除
 int deleteById(Serializable id);
 // 根据 columnMap 条件,删除记录
 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

3.7、MP启动注入SQL原理分析

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

前置知识:MyBatis源码中比较重要的对象,MyBatis框架的执行流程,Configuration,MappedStatement,……

  • Mapper本质是MapperProxy

  • MapperProxy中SqlSession --> SqlSessionFactory

  • SqlSessionFactory --> configuration --> MappedStatements,每一个mappedStatement都表示Mapper接口中的一个方法与Mapper映射文件中的一个SQL。MP在启动阶段就会逐一分析xxxMapper中的方法,并且将对应的SQL语句处理好,保存到configuration对象中的mappedStatements中

  • SqlMethod 枚举对象,MP支持的SQL方法,定义SQL的生成模板

  • TableInfo 数据库表反射信息,可以获取到数据库表相关的信息

  • SqlSource SQL语句处理对象

  • MapperBuilderAssistant 用于缓存、SQL参数、查询返回结果集处理等,将每一个mappedStatement添加到configuration中的MappedStatement

4、条件构造器

4.1、AbstractWrapper

MP通过Wrapper(MP封装的一个查询条件构造器)主要用于处理Sql拼接、排序、实体参数查询等

AbstractWrapper:QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类 用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件

警告:

不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输

正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作

可使用的条件如下:

分类条件代码SQL语句
比较 全部等于 all equal(可使用lambda表达式) allEq  
比较 等于 = equal eq =
比较 不等于 != <> not equal ne <>
比较 大于 > greater than gt >
比较 大于等于 >= greater equal ge >=
比较 小于 < little than lt <
比较 小于等于 little equal le <=
区间范围BETWEEN 在a和b之间 between BETWEEN a AND b
区间范围BETWEEN 不在a和b之间 notBetween NOT BETWEEN a AND b
模糊查询 类似 like LIKE '%值%'
模糊查询 不类似 notLike NOT LIKE '%值%'
模糊查询 不确定部分在左侧 likeLeft LIKE '%值'
模糊查询 不确定部分在右侧 likeRight LIKE '值%'
空判断 字段为空 isNull 字段 IS NULL
空判断 字段不为空 isNotNull 字段 IS NOT NULL
集合范围IN 在这个可数范围内 in 字段 IN (value.get(0), value.get(1), ...) 或 字段 IN (v0, v1, ...)
集合范围IN 不在这个可数范围内 notIn 字段 NOT IN (value.get(0), value.get(1), ...) 或 字段 NOT IN (v0, v1, ...)
子查询 在sql查询的范围内 inSql 字段 IN (sql语句,一般为查询语句select)
子查询 不在sql查询的范围内 notInSql 字段 NOT IN (sql语句,一般为查询语句select)
分组 分组 groupBy GROUP BY 字段, …
排序 从小到大排序 orderByAsc ORDER BY 字段, ... ASC
排序 从大到小排序 orderByDesc ORDER BY 字段, ... DESC
排序 排序(条件condition:isAsc设置为true则为ASC) orderBy ORDER BY 字段 ASC/DESC, ...
合计函数 搭配合计函数,类似于where having HAVING ( sql语句 )
动态Sql 主要方便在出现if...else下调用不同方法能不断链 func 用于动态sql加入if判断
拼接/嵌套 或(嵌套使用lambda表达式) or OR
拼接/嵌套 和(嵌套使用lambda表达式) and AND
正常嵌套,不带 AND 或者 OR 类似于几个条件作为一个整体(使用lambda表达式) nested ()
拼接 拼接Sql apply  
拼接 无视优化规则直接拼接到 sql 的最后 last 一般用于LIMIT
拼接 是否存在? exists EXISTS ( sql语句 )
拼接 是否不存在? notExists NOT EXISTS ( sql语句 )
查询 过滤查询条件 select SELECT
更新 设置 set SET 字段
更新 设置Sql setSql SET (Sql语句)

4.2、各条件举例

4.2.1、比较allEq、eq、ne、gt、ge、lt、le

 allEq(Map<R, V> params)
 allEq(Map<R, V> params, boolean null2IsNull)
 allEq(boolean condition, Map<R, V> params, boolean null2IsNull)

个别参数说明:

params : key为数据库字段名,value为字段值 null2IsNull : 为true则在mapvaluenull时调用 isNull 方法,为false时则忽略valuenull

例1: allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null

例2: allEq({id:1,name:"老王",age:null}, false)--->id = 1 and name = '老王'

 allEq(BiPredicate<R, V> filter, Map<R, V> params)
 allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
 allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

个别参数说明:

filter : 过滤函数,是否允许字段传入比对条件中 paramsnull2IsNull : 同上

例1: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})--->name = '老王' and age is null

例2: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)--->name = '老王'

例: eq("name", "老王")--->name = '老王'

例: ne("name", "老王")--->name <> '老王'

例: gt("age", 18)--->age > 18

例: ge("age", 18)--->age >= 18

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

例: le("age", 18)--->age <= 18

4.2.2、区间范围between、notBetween

例: between("age", 18, 30)--->age between 18 and 30

例: notBetween("age", 18, 30)--->age not between 18 and 30

4.2.3、模糊查询like、notLike、likeLeft、likeRight

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

例: notLike("name", "王")--->name not like '%王%'

例: likeLeft("name", "王")--->name like '%王'

例: likeRight("name", "王")--->name like '王%'

4.2.4、空判断isNull、isNotNull

例: isNull("name")--->name is null

例: isNotNull("name")--->name is not null

4.2.5、集合范围in、notIn

 in(R column, Collection<?> value)
 in(boolean condition, R column, Collection<?> value)

可以使用集合作为第二个参数

例: in("age",{10,20,30})--->age in (10,20,30)

或者直接列举值

例: in("age", 10, 20, 30)--->age in (10,20,30)

例: notIn("class",{1,2,3})--->class not in (1,2,3)

例: notIn("class", 1, 2, 3)--->classe not in (1,2,3)

4.2.6、子查询inSql、notInSql

例: 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("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")--->id not in (select id from table where id < 3)

4.2.7、分组groupBy

例: groupBy("id", "name")--->group by id,name

4.2.8、排序orderByAsc、orderByDesc、orderBy

例: orderByAsc("id", "name")--->order by id ASC,name ASC

例: orderByDesc("id", "name")--->order by id DESC,name DESC

可以不同的字段按照不同的顺序排序,优先性从左到右,默认true为ASC升序

例: orderBy(true, true, "id", "name")--->order by id ASC,name ASC

4.2.9、合计函数having

搭配合计函数,如count、sum、avg,类似于where

  • 例: having("sum(age) > 10")--->having sum(age) > 10

  • 例: having("sum(age) > {0}", 11)--->having sum(age) > 11

4.2.10、动态Sql语句func

在出现if...else下调用不同方法能不断链

例: func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})

4.2.11、拼接or、and、nested、apply、last

注意事项:

主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

例: eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'

使用嵌套时

例: or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着')

例: and(i -> i.eq("name", "李白").ne("status", "活着"))--->and (name = '李白' and status <> '活着')

正常嵌套 不带 AND 或者 OR 时使用nested,类似于()

例: nested(i -> i.eq("name", "李白").ne("status", "活着"))--->(name = '李白' and status <> '活着')

apply拼接

 apply(String applySql, Object... params)
 apply(boolean condition, String applySql, Object... params)

注意事项:

apply可用于数据库函数动态入参的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无视优化规则直接拼接到 sql 的最后

注意事项:

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

例: last("limit 1")

4.2.12、存在判断exist、notExists

例: exists("select id from table where age = 1")--->exists (select id from table where age = 1)

例: notExists("select id from table where age = 1")--->not exists (select id from table where age = 1)

旧版的andNew、orNew已经被Lambda表达式取代,直接使用and、or实现嵌套

4.2.13、过滤查询条件select

只查询出相应字段,属于QueryWrapper方法

例: select("id", "name", "age")

4.2.14、更新set、setSql

可用于增加修改的字段,增加update语句中set和where之间的sql片段

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件

  • 例: set("name", "老李头")

  • 例: set("name", "")--->数据库字段值变为空字符串

  • 例: set("name", null)--->数据库字段值变为null

  • 例: setSql("name = '老李头'")

 

4.3、多条件

支持链式编程,条件可以往后一直添加,条件对应的都是第一个为表字段名,后边为条件值。

在user表中多增加几个数据

1.在user表中,查询年龄在18~50之间且性别为男且姓名为Tom的所有用户

 Page<User> userPage = userMapper.selectPage(new Page<>(1,2),new QueryWrapper<User>()
         .between("age",18,50)
         .eq("gender",1)
         .eq("last_name","Tom")
 );

selectPage实现简单分页,每页2条记录,查询第1页

2.在user表中,查询性别为女并且名字中带有“老师”或者邮箱中带有“a”

复制代码
 List<User> userList = userMapper.selectList(new QueryWrapper<User>()
         .eq("gender", 0)
         .like("last_name", "老师")
         //.orNew()      //SQL:(gender = ? AND last_name LIKE ? OR email LIKE ?)
         //.like("email", "a")
         .or(i -> {
             i.like("email", "a");       //SQL:(gender = ? AND last_name LIKE ?) OR (email LIKE ?)
         })
 );
复制代码

3.在user表中,查询性别为女、根据age进行排序DESC,查前5个

 List<User> userList = userDao.selectList(new QueryWrapper<User>()
         .eq("gender", 0)
         .orderByDesc("age")
         .last("limit 0,5")
 );

5、代码生成器

MP提供了大量的自定义设置,生成的代码完全能够满足各类型的需求

MP的代码生成器都是基于Java代码生成

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

MP的代码生成器可生成:实体类、Mapper接口、Mapper映射文件、Service层、Controller层

5.1、表及字段命名策略选择

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

这么做的原因就是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虑这点性能损耗,可以采用下划线,在生成代码时配置dbColumnUnderline属性。

5.2、最新版本安装

仅适用 3.5.1 以上版本,对历史版本的不兼容

导入依赖

 // 注意!!当前包未传递依赖 mp 包,需要自己引入
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-generator</artifactId>
     <version>Latest Version</version>
 </dependency>

快速生成代码示例:

示例一:FastAutoGeneratorTest

复制代码
package com.baomidou.mybatisplus.generator.samples;
 ​
 import com.baomidou.mybatisplus.generator.FastAutoGenerator;
 import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
 import org.apache.ibatis.jdbc.ScriptRunner;
 ​
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.sql.Connection;
 import java.sql.SQLException;
 ​
 /**
  * <p>
  * 快速生成
  * </p>
  *
  * @author lanjerry
  * @since 2021-09-16
  */
 public class FastAutoGeneratorTest {
 ​
     /**
      * 执行初始化数据库脚本init.sql
      */
     public static void before() throws SQLException {
         Connection conn = DATA_SOURCE_CONFIG.build().getConn();
         InputStream inputStream = H2CodeGeneratorTest.class.getResourceAsStream("/sql/init.sql");
         ScriptRunner scriptRunner = new ScriptRunner(conn);
         scriptRunner.setAutoCommit(true);
         scriptRunner.runScript(new InputStreamReader(inputStream));
         conn.close();
     }
 ​
     /**
      * 数据源配置url、username、password
      */
     private static final DataSourceConfig.Builder DATA_SOURCE_CONFIG = new DataSourceConfig
         .Builder("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;MODE=MYSQL", "sa", "");
 ​
     /**
      * 执行 run
      */
     public static void main(String[] args) throws SQLException {
         before();
         FastAutoGenerator.create(DATA_SOURCE_CONFIG)
             // 全局配置
             .globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")).fileOverride())
             // 包配置
             .packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
             // 策略配置
             .strategyConfig(builder -> builder.addInclude("t_simple"))
             /*
                 模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
                .templateEngine(new BeetlTemplateEngine())
                .templateEngine(new FreemarkerTemplateEngine())
              */
             .execute();
     }
 }
复制代码

示例二:H2CodeGeneratorTest,以H2数据库为例

https://github.com/baomidou/generator/blob/develop/mybatis-plus-generator/src/test/java/com/baomidou/mybatisplus/generator/samples/H2CodeGeneratorTest.java

复制代码
 package com.baomidou.mybatisplus.generator.samples;
 ​
 import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.generator.AutoGenerator;
 import com.baomidou.mybatisplus.generator.config.*;
 import com.baomidou.mybatisplus.generator.engine.BeetlTemplateEngine;
 import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
 import com.baomidou.mybatisplus.generator.fill.Column;
 import com.baomidou.mybatisplus.generator.fill.Property;
 import org.apache.ibatis.jdbc.ScriptRunner;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 ​
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.Map;
 ​
 /**
  * H2 代码生成
  *
  * @author hubin,lanjerry
  * @since 1.0
  */
 public class H2CodeGeneratorTest {
 ​
     /**
      * 执行初始化数据库脚本init.sql
      */
     @BeforeAll
     public static void before() throws SQLException {
         Connection conn = DATA_SOURCE_CONFIG.getConn();
         InputStream inputStream = H2CodeGeneratorTest.class.getResourceAsStream("/sql/init.sql");
         ScriptRunner scriptRunner = new ScriptRunner(conn);
         scriptRunner.setAutoCommit(true);
         scriptRunner.runScript(new InputStreamReader(inputStream));
         conn.close();
     }
 ​
     /**
      * 数据源配置url、username、password
      */
     private static final DataSourceConfig DATA_SOURCE_CONFIG = new DataSourceConfig
         .Builder("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;MODE=MYSQL", "sa", "")
         .build();
 ​
     /**
      * 策略配置
      */
     private StrategyConfig.Builder strategyConfig() {
         return new StrategyConfig.Builder().addInclude("t_simple"); // 设置需要生成的表名
     }
 ​
     /**
      * 全局配置
      */
     private GlobalConfig.Builder globalConfig() {
         return new GlobalConfig.Builder().fileOverride();
     }
 ​
     /**
      * 包配置
      */
     private PackageConfig.Builder packageConfig() {
         return new PackageConfig.Builder();
     }
 ​
     /**
      * 模板配置
      */
     private TemplateConfig.Builder templateConfig() {
         return new TemplateConfig.Builder();
     }
 ​
     /**
      * 注入配置
      */
     private InjectionConfig.Builder injectionConfig() {
         // 测试自定义输出文件之前注入操作,该操作再执行生成代码前 debug 查看
         return new InjectionConfig.Builder().beforeOutputFile((tableInfo, objectMap) -> {
             System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
         });
     }
 ​
     /**
      * 简单生成
      */
     @Test
     public void testSimple() {
         AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG);
         generator.strategy(strategyConfig().build());
         generator.global(globalConfig().build());
         generator.execute();
     }
 ​
     /**
      * 过滤表前缀(后缀同理,支持多个)
      * result: t_simple -> simple
      */
     @Test
     public void testTablePrefix() {
         AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG);
         generator.strategy(strategyConfig().addTablePrefix("t_", "c_").build());
         generator.global(globalConfig().build());
         generator.execute();
     }
 ​
     /**
      * 过滤字段后缀(前缀同理,支持多个)
      * result: deleted_flag -> deleted
      */
     @Test
     public void testFieldSuffix() {
         AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG);
         generator.strategy(strategyConfig().addFieldSuffix("_flag").build());
         generator.global(globalConfig().build());
         generator.execute();
     }
 ​
     /**
      * 乐观锁字段设置
      * result: 新增@Version注解
      * 填充字段设置
      * result: 新增@TableField(value = "xxx", fill = FieldFill.xxx)注解
      */
     @Test
     public void testVersionAndFill() {
         AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG);
         generator.strategy(strategyConfig().entityBuilder()
             .versionColumnName("version") // 基于数据库字段
             .versionPropertyName("version")// 基于模型属性
             .addTableFills(new Column("create_time", FieldFill.INSERT))    //基于数据库字段填充
             .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))    //基于模型属性填充
             .build());
         generator.global(globalConfig().build());
         generator.execute();
     }
 ​ 
    /** 
     * 逻辑删除字段设置 
     * result: 新增@TableLogic注解 
     * 忽略字段设置 
     * result: 不生成 
     */ 
    @Test 
    public void testLogicDeleteAndIgnoreColumn() { 
        AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG); 
        generator.strategy(strategyConfig().entityBuilder() 
            .logicDeleteColumnName("deleted") // 基于数据库字段 
            .logicDeletePropertyName("deleteFlag")// 基于模型属性 
            .addIgnoreColumns("age") // 基于数据库字段 
            .build()); 
        generator.global(globalConfig().build()); 
        generator.execute(); 
    } 
​ 
    /** 
     * 自定义模板生成的文件名称 
     * result: TSimple -> TSimpleEntity 
     */ 
    @Test 
    public void testCustomTemplateName() { 
        AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG); 
        generator.strategy(strategyConfig() 
            .entityBuilder().formatFileName("%sEntity") 
            .mapperBuilder().formatMapperFileName("%sDao").formatXmlFileName("%sXml") 
            .controllerBuilder().formatFileName("%sAction") 
            .serviceBuilder().formatServiceFileName("%sService").formatServiceImplFileName("%sServiceImp") 
            .build()); 
        generator.global(globalConfig().build()); 
        generator.execute(); 
    } 
​ 
    /** 
     * 自定义模板生成的文件路径 
     * 
     * @see OutputFile 
     */ 
    @Test 
    public void testCustomTemplatePath() { 
        // 设置自定义路径 
        Map<OutputFile, String> pathInfo = new HashMap<>(); 
        pathInfo.put(OutputFile.mapperXml, "D://"); 
        pathInfo.put(OutputFile.entity, "D://entity//"); 
        AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG); 
        generator.strategy(strategyConfig().build()); 
        generator.packageInfo(packageConfig().pathInfo(pathInfo).build()); 
        generator.global(globalConfig().build()); 
        generator.execute(); 
    } 
​ 
    /** 
     * 自定义模板entity1 
     */ 
    @Test 
    public void testCustomTemplate() { 
        AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG); 
        generator.strategy(strategyConfig().build()); 
        generator.template(templateConfig() 
            .entity("/templates/entity1.java") 
            .build()); 
        generator.global(globalConfig().build()); 
        generator.execute(); 
    } 
​ 
    /** 
     * 自定义注入属性 
     */ 
    @Test 
    public void testCustomMap() { 
        // 设置自定义属性 
        Map<String, Object> map = new HashMap<>(); 
        map.put("abc", 123); 
        AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG); 
        generator.strategy(strategyConfig().build()); 
        generator.template(templateConfig() 
            .entity("/templates/entity1.java") 
            .build()); 
        generator.injection(injectionConfig().customMap(map).build()); 
        generator.global(globalConfig().build()); 
        generator.execute(); 
    } 
​ 
    /** 
     * 自定义文件 
     * key为文件名称,value为文件路径 
     * 输出目录为 other 
     */ 
    @Test 
    public void testCustomFile() { 
        // 设置自定义输出文件 
        Map<String, String> customFile = new HashMap<>(); 
        customFile.put("test.txt", "/templates/test.vm"); 
        AutoGenerator generator = new AutoGenerator(DATA_SOURCE_CONFIG); 
        generator.strategy(strategyConfig().build()); 
        generator.injection(injectionConfig().customFile(customFile).build()); 
        generator.global(globalConfig().build()); 
        generator.execute(); 
    } 
}
复制代码

5.3、组件说明

5.3.1、数据库配置(DataSourceConfig)

1、基础配置

属性说明示例
url jdbc路径 jdbc:mysql://127.0.0.1:3306/mybatis-plus
username 数据库账号 root
password 数据库密码 123456
 new DataSourceConfig.
     Builder("jdbc:mysql://127.0.0.1:3306/mybatis-plus","root","123456").build();

2、可选配置

方法说明示例
dbQuery(IDbQuery) 数据库查询 new MySqlQuery()
schema(String) 数据库schema(部分数据库适用) mybatis-plus
typeConvert(ITypeConvert) 数据库类型转换器 new MySqlTypeConvert()
keyWordsHandler(IKeyWordsHandler) 数据库关键字处理器 new MySqlKeyWordsHandler()
 new DataSourceConfig.Builder("jdbc:mysql://127.0.0.1:3306/mybatis-plus","root","123456")
    .dbQuery(new MySqlQuery())
    .schema("mybatis-plus")
    .typeConvert(new MySqlTypeConvert())
    .keyWordsHandler(new MySqlKeyWordsHandler())
    .build();

3、全局配置-globalconfig全局配置(GlobalConfig)

方法说明示例
fileOverride 覆盖已生成文件 默认值:false
disableOpenDir 禁止打开输出目录 默认值:true
outputDir(String) 指定输出目录 /opt/baomidou/ 默认值: windows:D:// linux or mac : /tmp
author(String) 作者名 baomidou 默认值:作者
enableKotlin 开启 kotlin 模式 默认值:false
enableSwagger 开启 swagger 模式 默认值:false
dateType(DateType) 时间策略 DateType.ONLY_DATE 默认值: DateType.TIME_PACK
commentDate(String) 注释日期 默认值: yyyy-MM-dd
 new GlobalConfig.Builder()
    .fileOverride()
    .outputDir("/opt/baomidou")
    .author("baomidou")
    .enableKotlin()
    .enableSwagger()
    .dateType(DateType.TIME_PACK)
    .commentDate("yyyy-MM-dd")
    .build();

5.3.2、包配置(PackageConfig)

方法说明示例
parent(String) 父包名 默认值:com.baomidou
moduleName(String) 父包模块名 默认值:无
entity(String) Entity 包名 默认值:entity
service(String) Service 包名 默认值:service
serviceImpl(String) Service Impl 包名 默认值:service.impl
mapper(String) Mapper 包名 默认值:mapper
mapperXml(String) Mapper XML 包名 默认值:mapper.xml
controller(String) Controller 包名 默认值:controller
other(String) 自定义文件包名 输出自定义文件时所用到的包名
pathInfo(Map<OutputFile, String>) 路径配置信息 Collections.singletonMap(OutputFile.mapperXml, "D://")
 new PackageConfig.Builder()
    .parent("com.baomidou.mybatisplus.samples.generator")
    .moduleName("sys")
    .entity("po")
    .service("service")
    .serviceImpl("service.impl")
    .mapper("mapper")
    .mapperXml("mapper.xml")
    .controller("controller")
    .other("other")
    .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://"))
    .build();

5.3.3、模板配置(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
 new TemplateConfig.Builder()
    .disable(TemplateType.ENTITY)
    .entity("/templates/entity.java")
    .service("/templates/service.java")
    .serviceImpl("/templates/serviceImpl.java")
    .mapper("/templates/mapper.java")
    .mapperXml("/templates/mapper.xml")
    .controller("/templates/controller.java")
    .build();

5.3.4、注入配置(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")
 new InjectionConfig.Builder()
    .beforeOutputFile((tableInfo, objectMap) -> {
     System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
    })
    .customMap(Collections.singletonMap("test", "baomidou"))
    .customFile(Collections.singletonMap("test.txt", "/templates/test.vm"))
    .build();

5.3.5、策略配置(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 策略配置  
 new StrategyConfig.Builder()
    .enableCapitalMode()
    .enableSkipView()
    .disableSqlFilter()
    .likeTable(new LikeTable("USER"))
    .addInclude("t_simple")
    .addTablePrefix("t_", "c_")
    .addFieldSuffix("_flag")
    .build();

1、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
enableTableFieldAnnotationEnable 开启生成实体时生成字段注解 默认值: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) 添加表字段填充  
idType(IdType) 全局主键类型  
convertFileName(ConverterFileName) 转换文件名称  
formatFileName(String) 格式化文件名称  
 new StrategyConfig.Builder()
    .entityBuilder()
    .superClass(BaseEntity.class)
    .disableSerialVersionUID()
    .enableChainModel()
    .enableLombok()
    .enableRemoveIsPrefix()
    .enableTableFieldAnnotation()
    .enableActiveRecord()
    .versionColumnName("version")
    .versionPropertyName("version")
    .logicDeleteColumnName("deleted")
    .logicDeletePropertyName("deleteFlag")
    .naming(NamingStrategy.no_change)
    .columnNaming(NamingStrategy.underline_to_camel)
    .addSuperEntityColumns("id", "created_by", "created_time", "updated_by", "updated_time")
    .addIgnoreColumns("age")
    .addTableFills(new Column("create_time", FieldFill.INSERT))
    .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))
    .idType(IdType.AUTO)
    .formatFileName("%sEntity")
    .build();

2、Controller策略配置

方法说明示例
superClass(Class<?>) 设置父类 BaseController.class
superClass(String) 设置父类 com.baomidou.global.BaseController
enableHyphenStyle 开启驼峰转连字符 默认值:false
enableRestStyle 开启生成@RestController 控制器 默认值:false
convertFileName(ConverterFileName) 转换文件名称  
formatFileName(String) 格式化文件名称  
 new StrategyConfig.Builder()
    .controllerBuilder()
    .superClass(BaseController.class)
    .enableHyphenStyle()
    .enableRestStyle()
    .formatFileName("%sAction")
    .build();

3、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 实现类文件名称  
 new StrategyConfig.Builder()
    .serviceBuilder()
    .superServiceClass(BaseService.class)
    .superServiceImplClass(BaseServiceImpl.class)
    .formatServiceFileName("%sService")
    .formatServiceImplFileName("%sServiceImp")
    .build();

4、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 实现类文件名称  
 new StrategyConfig.Builder()
    .mapperBuilder()
    .superClass(BaseMapper.class)
    .enableMapperAnnotation()
    .enableBaseResultMap()
    .enableBaseColumnList()
    .cache(MyMapperCache.class)
    .formatMapperFileName("%sDao")
    .formatXmlFileName("%sXml")
    .build();

6、插件扩展

6.1、MyBatis插件机制

MyBatis通过插件(interceptor)可以做到拦截四大对象相关方法的执行。根据需求,完成相关数据的动态改变。分别为

Executor 执行器对象,MyBatis的执行器,用于执行增删改查操作

StatementHandler 编译器对象,数据库的处理对象,用于执行SQL语句

ParameterHandler Sql参数处理器对象,处理SQL的参数对象

ResultSetHandler 结果集处理器对象,处理SQL的返回结果集

插件原理:四大对象的每个对象在创建时,都会执行interceptorChain.pluginAll(),会经过每个插件对象的plugin()方法,目的是为当前的四大对象创建代理,代理对象就可以拦截到四大对象相关方法的执行,要执行四大对象的方法都需要经过代理。

运行过程:

6.2、插件主体MybatisPlusInterceptor

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

定义接口:

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

插件都将基于此接口来实现功能

目前已有的功能:

  • 自动分页: PaginationInnerInterceptor

  • 多租户: TenantLineInnerInterceptor

  • 动态表名: DynamicTableNameInnerInterceptor

  • 乐观锁: OptimisticLockerInnerInterceptor

  • sql性能规范: IllegalSQLInnerInterceptor

  • 防止全表更新与删除: BlockAttackInnerInterceptor

注意:

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

  • 多租户,动态表名

  • 分页,乐观锁

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

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

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

6.3.1、在Spring中注册

只考虑MP版本在3.4.0之后的引入

3.4.0版本对此部分有更新,如果是旧版本升级,会出现分页失效问题,同时idea会提示PaginationInterceptor过时,新版本改用了MybatisPlusInterceptor

复制代码
 <property name="plugins">
   <array>
 <!--  <bean class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor">-->
 <!--  </bean>-->
   <bean class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
    <property name="interceptors">
    <list>
     <bean class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor"></bean>
    </list>
    </property>
   </bean>
   </array>
 </property>
复制代码

6.3.2、在SpringBoot中配置

方式一、加入配置类

复制代码
 @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.MYSQL));
         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="MYSQL"/>
   </plugin>
 </plugins>

6.3.3、PaginationInnerInterceptor

属性property可设置

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

6.3.4、简单使用

复制代码
 @Test
 public void testPage(){
 ​
     Page<User> page = new Page<>(1, 1);
     Page<User> userPage = userMapper.selectPage(page, null);
     System.out.println(userPage.getRecords());
 ​
     System.out.println("============获取分页相关的一些信息===============");
 ​
     System.out.println("总条数:" + page.getTotal());
     System.out.println("当前页码:" + page.getCurrent());
     System.out.println("总页码:" + page.getPages());
     System.out.println("每页显示的条数:" + page.getSize());
     System.out.println("是否有上一页:" + page.hasPrevious());
     System.out.println("是否有下一页:" + page.hasNext());
 ​
 }
复制代码

输出结果示例:

[User(id=1, userName=admin, password=$2a$10$ByCFyT0Fyp6kKr98YPIDh.LTzg26xw4yU9uFbZDuR/NMNPIWJyNN2, password2=$2a$10$ByCFyT0Fyp6kKr98YPIDh.LTzg26xw4yU9uFbZDuR/NMNPIWJyNN2, realName=用户admin, birthday=2021-11-01, sex=null, deptId=1, deptName=研发部, email=null, tel=null, postId=null, postName=1, principal=false, desc=0, roleIds=null, lock=false)] ============获取分页相关的一些信息=============== 总条数:3 当前页码:1 总页码:3 每页显示的条数:1 是否有上一页:false 是否有下一页:true

6.4、SqlExplainInterceptor执行分析插件(新版本:BlockAttackInnerInterceptor)

参数:stopProceed 发现执行全表 delete update 语句是否停止执行

6.4.1、MP版本3.4.0之前

Spring配置

复制代码
 <property name="plugins">
    <list>
     <!-- 注册分页插件 -->
     <bean class="com.baomidou.mybatisplus.plugins.PaginationInterceptor"></bean>
     <!-- 注册执行分析插件 -->
     <bean class="com.baomidou.mybatisplus.plugins.SqlExplainInterceptor">
      <property name="stopProceed" value="true" />
     </bean>
    </list>
 </property> 
复制代码

测试文件

  @Test
  public void testExplainPlugin() {
  //不传任何条件 所以是删除全表操作
  userMapper.delete(null);
  }

发现是删除全表操作,立即终止

6.4.2、MP版本3.4.0之后

MP版本3.4.0之后,已改名为BlockAttackInnerInterceptor,新版本已经默认执行stopProceed

Spring配置

复制代码
 <property name="plugins">
    <array>
 <!--    <bean class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor">-->
 <!--    </bean>-->
 <!--    <bean class="com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor"></bean>-->
     <bean class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
      <property name="interceptors">
       <list>
        <bean class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor"></bean>
        <bean class="com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor"></bean>
       </list>
      </property>
     </bean>
    </array>
 </property>
复制代码

SpringBoot配置

复制代码
 @Bean
 public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
 ​
    List<InnerInterceptor> list = new ArrayList<>();
    list.add(new BlockAttackInnerInterceptor());
    interceptor.setInterceptors(list);
 ​
    return interceptor;
 }
复制代码

测试全表更新

 @Test
 public void testUpdateAll(){
    User user = new User();
    user.setAge(18);  //全表更新数据
boolean result = user.update(null);
    System.out.println("result==>"+result);
 }

测试抛出PersistenceException异常,阻止全表操作,测试单条记录操作不受影响

6.4.3、原理分析

拼接EXPLAIN进行分析。判断输入Sql使用的更新或删除方法是否使用了WHERE条件,未使用则认为为全表操作,调用stopProceed参数,停止操作并抛出异常。否则proceed继续执行。

只建议在开发场景使用,不建议在实际运行环境使用。

6.5、OptimisticLockerInterceptor乐观锁插件

当需要更新一条记录时,希望这条记录没有被别人更新

乐观锁的实现原理:取出记录时,获取当前version;更新时带上这个version;执行更新时,set version = oldVersion + 1 where version = oldVersion;如果version不对,更新失败。

@Version注解实体类字段version,数据库也必须有这个字段,可设置默认值为1。加上getter、setter方法(Lombok @Data)

 @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 不能复用!!!

6.5.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>
复制代码

方式二、SpringBoot添加@Bean

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

6.5.2、示例

复制代码
 // 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;
     }
 }
复制代码

7、自定义全局方法

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

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

具体用法见9、应用示例

8、公共字段自动填充

实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

查看定义FieldFill

复制代码
 public enum FieldFill {
     /**
      * 默认不处理
      */
     DEFAULT,
     /**
      * 插入填充字段
      */
     INSERT,
     /**
      * 更新填充字段
      */
     UPDATE,
     /**
      * 插入和更新填充字段
      */
     INSERT_UPDATE
 }
复制代码

1.注解填充字段 @TableField(fill = FieldFill.INSERT)

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

2.自定义实现类 MyMetaObjectHandler

MetaObject 元对象,是MyBatis提供的一个用于更加方便、优雅的访问对象的属性,给对象的属性设置值对的一个对象,支持对Object、Map、Collection等对象进行包装。

本质上metaObject获取对象的属性值或者是给对象的属性设置值,最终是通过Reflector获取到属性的随影方法的invoker,最终invoke

复制代码
 @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)
     }
 }
复制代码

示例:自动填充name

复制代码
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.reflection.MetaObject;
 import org.springframework.stereotype.Component;
 ​
 /**
  * @Name MyMetaObjectHandler
  * @Description 自定义公共字段填充处理器
  * @Author 88534
  * @Date 2021/12/5 19:13
  */
 @Slf4j
 @Component
 public class MyMetaObjectHandler implements MetaObjectHandler {
     /**
      * 插入操作 name自动填充xxx
      * @param metaObject
      */
     @Override
     public void insertFill(MetaObject metaObject) {
         // 获取到需要被填充的字段的值
         Object fieldValue = getFieldValByName("name", metaObject);
         if (fieldValue == null) {
             log.info("插入操作,满足填充条件");
             setFieldValByName("name","xxx",metaObject);
         }
     }
 ​
     /**
      * 修改操作 name自动填充yyy
      * @param metaObject
      */
     @Override
     public void updateFill(MetaObject metaObject) {
         // 获取到需要被填充的字段的值
         Object fieldValue = getFieldValByName("name", metaObject);
         if (fieldValue == null) {
             log.info("更新操作,满足填充条件");
             setFieldValByName("name","yyy",metaObject);
         }
     }
 }
复制代码

注意事项:

  • 填充原理是直接给entity的属性设置值!!!

  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null

  • MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充

  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段

  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入

  • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法

  • 不需要根据任何来区分可以使用父类的fillStrategy方法

3.MP全局注入自定义公共字段填充处理器

复制代码
 @Bean
 public MetaObjectHandler metaObjectHandler() {
     return new MyMetaObjectHandler();
 }
 ​
 @Bean
 public GlobalConfig globalConfig(){
     GlobalConfig globalConfig = new GlobalConfig();
     globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
     return globalConfig;
 }
复制代码

9、MP扩展功能整合项目

参考文档:https://gitee.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-deluxe

该项目演示以下功能:

  1. 逻辑删除

  2. 自动填充

  3. 自定义全局方法:insert/insertBatch

9.1、环境搭建

9.1.1、创建数据库表user

设置id, name, age, email, version, deleted字段

复制代码
 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 '邮箱',
   version INT(10) NULL DEFAULT 1 COMMENT '乐观锁版本',
   deleted INT (11) NULL DEFAULT 1 COMMENT '逻辑删除字段',
   create_time TIMESTAMP NULL,
   PRIMARY KEY (id)
 );
复制代码

插入一些数据

复制代码
 INSERT INTO user (id, name, age, email, version, deleted)
 VALUES (1, 'Jone', 18, 'test1@baomidou.com',    1, 0),
        (2, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (3, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (4, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (5, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (6, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (7, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (8, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (9, 'Jack', 20, 'test2@baomidou.com',    1, 0),
        (10, 'Jack', 20, 'test2@baomidou.com',   1, 0),
        (11, 'Jack', 20, 'test2@baomidou.com',   1, 0),
        (12, 'Jack', 20, 'test2@baomidou.com',   1, 0),
        (13, 'Jack', 20, 'test2@baomidou.com',   1, 0),
        (14, 'Jack', 20, 'test2@baomidou.com',   1, 0),
        (15, 'Tom', 28, 'test3@baomidou.com',    1, 0),
        (16, 'Sandy', 21, 'test4@baomidou.com',  1, 0),
        (17, 'Billie', 24, 'test5@baomidou.com', 1, 0);
复制代码

9.1.2、创建一个SpringBoot项目mybatis-plus-samples

创建一个模块mybatis-plus-sample-deluxe

1.导入依赖pom.xml

以H2数据库为例,可以更换为MySql

复制代码
 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://maven.apache.org/POM/4.0.0"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <artifactId>mybatis-plus-samples</artifactId>
         <groupId>com.baomidou</groupId>
         <version>0.0.1-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion><artifactId>mybatis-plus-sample-deluxe</artifactId><dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter</artifactId>
         </dependency>
         <dependency>
             <groupId>com.h2database</groupId>
             <artifactId>h2</artifactId>
             <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
         </dependency>
     </dependencies><build>
         <plugins>
             <plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
 </project>
复制代码

2.配置application.yml,配置Mapper扫描路径

 # DataSource Config
 spring:
  datasource:
    driver-class-name: org.h2.Driver
    schema: classpath:db/schema-h2.sql
    data: classpath:db/data-h2.sql
    url: jdbc:h2:mem:test
    username: root
    password: test
 
 # Logger Config
 logging:
  level:
    com.baomidou.mybatisplus.samples.deluxe: debug
 
 mybatis-plus:
   # 扫描 mapper.xml
  mapper-locations: classpath:/mapper/*Mapper.xml
  configuration:
    jdbc-type-for-null: 'null'

3.创建实体类User

(1)添加逻辑删除字段,使用@TableLogic(value = "0", delval = "1")

效果:在字段上加上这个注解再执行BaseMapper的删除方法时,删除方法会变成修改

value = "" 未删除的值,默认值为0 delval = "" 删除后的值,默认值为1 @TableLogic(value="原值",delval="改值")

(2)@TableField设置fill自动填充字段,针对场景为insert插入

设置select = false可以在查询时不会出现逻辑删除的字段

设置typeHandler设置数据的自定义处理方式,处理如下:

(3)@Accessors(chain = true)支持链式编程,定义时可以采用链式定义

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.config;
 ​
 import org.apache.ibatis.type.BaseTypeHandler;
 import org.apache.ibatis.type.JdbcType;
 ​
 import java.sql.CallableStatement;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 ​
 /**
  * 测试自定义 typeHandler
  *
  * @author miemie
  * @since 2018-08-13
  */
 public class TestTypeHandler extends BaseTypeHandler<String> {
 ​
     @Override
     public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
         ps.setString(i, "TestTypeHandler set {" + parameter + "}");
     }
 ​
     @Override
     public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
         String string = rs.getString(columnName);
         return "TestTypeHandler(rs columnName) get {" + string + "}";
     }
 ​
     @Override
     public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
         String string = rs.getString(columnIndex);
         return "TestTypeHandler(rs columnIndex) get {" + string + "}";
     }
 ​
     @Override
     public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
         String string = cs.getString(columnIndex);
         return "TestTypeHandler(cs columnIndex) get {" + string + "}";
     }
 }
复制代码

实体类创建如下:

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.entity;
 ​
 import java.sql.Timestamp;
 ​
 import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableLogic;
 import com.baomidou.mybatisplus.annotation.Version;
 ​
 import com.baomidou.mybatisplus.samples.deluxe.config.TestTypeHandler;
 import lombok.Data;
 import lombok.experimental.Accessors;
 ​
 /**
  * 用户表
  * 设置逻辑删除字段,并且逻辑删除字段不 select 出来
  *
  * @author miemie
  * @since 2018-08-12
  */
 @Data
 @Accessors(chain = true)
 public class User {
     private Long id;
     private String name;
     private Integer age;
     @TableField(typeHandler = TestTypeHandler.class)
     private String email;
 ​
     @Version
     private Integer version;
 ​
     @TableLogic(value = "0", delval = "1")
     @TableField(select = false)
     private Integer deleted;
 ​
     @TableField(value = "create_time", fill = FieldFill.INSERT)
     private Timestamp createTime;
 }
复制代码

4.创建自定义分页实体类UserPage

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.model;
 ​
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.samples.deluxe.entity.User;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 ​
 /**
  * 自定义分页
  *
  * @author miemie
  * @since 2018-08-13
  */
 @Data
 @Accessors(chain = true)
 @EqualsAndHashCode(callSuper = true)
 public class UserPage extends Page<User> {
     private static final long serialVersionUID = 7246194974980132237L;
 ​
     private Integer selectInt;
     private String selectStr;
 ​
     public UserPage(long current, long size) {
         super(current, size);
     }
 }
复制代码

9.1.3、基础业务

复制代码
1.创建UserMapper接口

 package com.baomidou.mybatisplus.samples.deluxe.mapper;
 ​
 import java.util.List;
 ​
 import org.apache.ibatis.annotations.Param;
 ​
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.samples.deluxe.config.MyBaseMapper;
 import com.baomidou.mybatisplus.samples.deluxe.entity.User;
 import com.baomidou.mybatisplus.samples.deluxe.model.UserPage;
 ​
 /**
  * @author miemie
  * @since 2018-08-12
  */
 public interface UserMapper extends MyBaseMapper<User> {
 ​
     /**
      * 自定义分页查询
      *
      * @param userPage 单独 user 模块使用的分页
      * @return 分页数据
      */
     UserPage selectUserPage(UserPage userPage);
 ​
     List<User> findList(@Param("user") User user);
 ​
     List<User> customerSqlSegment(@Param("ew") Wrapper ew);
 ​
 }
复制代码

2.在resources/mapper目录下创建UserMapper映射文件,执行Sql语句

复制代码
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
 <mapper namespace="com.baomidou.mybatisplus.samples.deluxe.mapper.UserMapper"><select id="selectUserPage" resultType="com.baomidou.mybatisplus.samples.deluxe.entity.User">
         select *
         from user
         <trim prefix="where" prefixOverrides="AND">
             <if test="selectInt != null">
                 age = #{selectInt}
             </if>
             <if test="selectStr != null">
                 AND name = #{selectStr}
             </if>
             AND deleted = 0
         </trim>
     </select>
     <!-- verify github #1532-->
     <select id="findList" parameterType="com.baomidou.mybatisplus.samples.deluxe.entity.User" resultType="com.baomidou.mybatisplus.samples.deluxe.entity.User">
         select * from user where name like concat(concat('%', #{user.name}), '%')
     </select><select id="customerSqlSegment" resultType="com.baomidou.mybatisplus.samples.deluxe.entity.User">
         select u.*
         from USER u
         ${ew.customSqlSegment}
     </select></mapper>
复制代码

9.2、自定义Sql注入

对MyBatis重要对象(如:MappedStatement)进行处理

9.2.1、自定义注入方法

继承AbstractMethod,重写injectMappedStatement方法,自行拼接Sql语句

1.删除全部:DeleteAll

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.methods;
 ​
 import com.baomidou.mybatisplus.core.injector.AbstractMethod;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.SqlSource;
 ​
 /**
  * 删除全部
  *
  * @author nieqiurong 2018/8/11 20:29.
  */
 public class DeleteAll extends AbstractMethod {
 ​
     @Override
     public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
         /* 执行 SQL ,动态 SQL 参考类 SqlMethod */
         String sql = "delete from " + tableInfo.getTableName();
         /* mapper 接口方法名一致 */
         String method = "deleteAll";
         SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
         return this.addDeleteMappedStatement(mapperClass, method, sqlSource);
     }
 }
复制代码

2.全部字段值改造进行外界输入:MyInsertAll

所有字段插入一次,产生一条新记录

插入全部为对应格式insert into 表名 字段1,字段2…… values (#{值1},#{值2},……)

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.methods;
 ​
 import org.apache.ibatis.executor.keygen.NoKeyGenerator;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.SqlSource;
 ​
 import com.baomidou.mybatisplus.core.injector.AbstractMethod;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 ​
 /**
  * @author yuxiaobin
  * @date 2019/6/14
  */
 public class MyInsertAll extends AbstractMethod {
 ​
     @Override
     public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
         String sql = "insert into %s %s values %s";
         StringBuilder fieldSql = new StringBuilder();
         fieldSql.append(tableInfo.getKeyColumn()).append(",");
         StringBuilder valueSql = new StringBuilder();
         valueSql.append("#{").append(tableInfo.getKeyProperty()).append("},");
         tableInfo.getFieldList().forEach(x->{
             fieldSql.append(x.getColumn()).append(",");
             valueSql.append("#{").append(x.getProperty()).append("},");
         });
         fieldSql.delete(fieldSql.length()-1, fieldSql.length());
         fieldSql.insert(0, "(");
         fieldSql.append(")");
         valueSql.insert(0, "(");
         valueSql.delete(valueSql.length()-1, valueSql.length());
         valueSql.append(")");
         SqlSource sqlSource = languageDriver.createSqlSource(configuration, String.format(sql, tableInfo.getTableName(), fieldSql.toString(), valueSql.toString()), modelClass);
         return this.addInsertMappedStatement(mapperClass, modelClass, "myInsertAll", sqlSource, new NoKeyGenerator(), null, null);
     }
 }
复制代码

3.批量插入:MysqlInsertAllBatch

prepareFieldSql处理所有字段

prepareValuesSqlForMysqlBatch批量添加数据

批量保存使用mysql特有语法:

 -- 示例
 insert into user(id, name, age) values (1, "a", 17), (2,"b", 18)
复制代码
package com.baomidou.mybatisplus.samples.deluxe.methods;
 ​
 import org.apache.ibatis.executor.keygen.NoKeyGenerator;
 import org.apache.ibatis.mapping.MappedStatement;
 import org.apache.ibatis.mapping.SqlSource;
 ​
 import com.baomidou.mybatisplus.core.injector.AbstractMethod;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 ​
 /**
  * @author yuxiaobin
  * @date 2019/6/14
  */
 public class MysqlInsertAllBatch extends AbstractMethod {
 ​
     @Override
     public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
         final String sql = "<script>insert into %s %s values %s</script>";
         final String fieldSql = prepareFieldSql(tableInfo);
         final String valueSql = prepareValuesSqlForMysqlBatch(tableInfo);
         final String sqlResult = String.format(sql, tableInfo.getTableName(), fieldSql, valueSql);
         SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass);
         return this.addInsertMappedStatement(mapperClass, modelClass, "mysqlInsertAllBatch", sqlSource, new NoKeyGenerator(), null, null);
     }
 ​
     private String prepareFieldSql(TableInfo tableInfo) {
         StringBuilder fieldSql = new StringBuilder();
         fieldSql.append(tableInfo.getKeyColumn()).append(",");
         tableInfo.getFieldList().forEach(x -> {
             fieldSql.append(x.getColumn()).append(",");
         });
         fieldSql.delete(fieldSql.length() - 1, fieldSql.length());
         fieldSql.insert(0, "(");
         fieldSql.append(")");
         return fieldSql.toString();
     }
 ​
     private String prepareValuesSqlForMysqlBatch(TableInfo tableInfo) {
         final StringBuilder valueSql = new StringBuilder();
         valueSql.append("<foreach collection=\"list\" item=\"item\" index=\"index\" open=\"(\" separator=\"),(\" close=\")\">");
         valueSql.append("#{item.").append(tableInfo.getKeyProperty()).append("},");
         tableInfo.getFieldList().forEach(x -> valueSql.append("#{item.").append(x.getProperty()).append("},"));
         valueSql.delete(valueSql.length() - 1, valueSql.length());
         valueSql.append("</foreach>");
         return valueSql.toString();
     }
 }
复制代码

4.创建MyBaseMapper接口继承BaseMapper

定义自定义的方法,需要与addInsertMappedStatement的参数method对应

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.config;
 ​
 import java.util.List;
 ​
 import org.apache.ibatis.annotations.Param;
 ​
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 ​
 /**
  * @author miemie
  * @since 2018-08-13
  */
 public interface MyBaseMapper<T> extends BaseMapper<T> {
 ​
     /**
      * 自定义通用方法
      */
     Integer deleteAll();
 ​
     int myInsertAll(T entity);
 ​
     /**
      * 如果要自动填充,@{@code Param}(xx) xx参数名必须是 list/collection/array 3个的其中之一
      *
      * @param batchList
      * @return
      */
     int mysqlInsertAllBatch(@Param("list") List<T> batchList);
 }
复制代码

9.2.2、实现注入

1.自定义 SqlInjector,加入MethodList

复制代码
 package com.baomidou.mybatisplus.samples.deluxe;
 ​
 import java.util.List;
 ​
 import com.baomidou.mybatisplus.core.injector.AbstractMethod;
 import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
 import com.baomidou.mybatisplus.core.injector.methods.SelectById;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import com.baomidou.mybatisplus.samples.deluxe.methods.DeleteAll;
 import com.baomidou.mybatisplus.samples.deluxe.methods.MyInsertAll;
 import com.baomidou.mybatisplus.samples.deluxe.methods.MysqlInsertAllBatch;
 ​
 /**
  * 自定义 SqlInjector
  *
  * @author miemie
  * @since 2018-08-13
  */
 public class MyLogicSqlInjector extends DefaultSqlInjector {
 ​
     /**
      * 如果只需增加方法,保留MP自带方法
      * 可以super.getMethodList() 再add
      * @return
      */
     @Override
     public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
         List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
         methodList.add(new DeleteAll());
         methodList.add(new MyInsertAll());
         methodList.add(new MysqlInsertAllBatch());
         methodList.add(new SelectById());
         return methodList;
     }
 }
复制代码

2.在全局配置中注册自定义方法MyLogicSqlInjector,同时加入分页和乐观锁组件(本质为拦截器MybatisPlusInterceptor)

复制代码
 package com.baomidou.mybatisplus.samples.deluxe.config;
 ​
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import com.baomidou.mybatisplus.samples.deluxe.MyLogicSqlInjector;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ​
 /**
  * @author miemie
  * @since 2018-08-12
  */
 @Configuration
 @MapperScan("com.baomidou.mybatisplus.samples.deluxe.mapper")
 public class MybatisPlusConfig {
 ​
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
         interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
         interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
         return interceptor;
     }
 ​
     /**
      * 自定义 SqlInjector
      * 里面包含自定义的全局方法
      */
     @Bean
     public MyLogicSqlInjector myLogicSqlInjector() {
         return new MyLogicSqlInjector();
     }
 }
复制代码

9.3、自动填充

实现MetaObjectHandler

复制代码
 package com.baomidou.mybatisplus.samples.deluxe;
 ​
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import org.apache.ibatis.reflection.MetaObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 ​
 import java.sql.Timestamp;
 ​
 /**
  * 填充器
  *
  * @author nieqiurong 2018-08-10 22:59:23.
  */
 @Component
 public class MyMetaObjectHandler implements MetaObjectHandler {
 ​
     private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);
 ​
     @Override
     public void insertFill(MetaObject metaObject) {
         LOGGER.info("start insert fill ....");
         //避免使用metaObject.setValue()
         this.strictInsertFill(metaObject, "createTime", Timestamp.class, new Timestamp(System.currentTimeMillis()));
     }
 ​
     @Override
     public void updateFill(MetaObject metaObject) {
         LOGGER.info("nothing to fill ....");
     }
 }
复制代码

9.4、测试功能

复制代码
 package com.baomidou.mybatisplus.samples.deluxe;
 ​
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.samples.deluxe.entity.User;
 import com.baomidou.mybatisplus.samples.deluxe.mapper.UserMapper;
 import com.baomidou.mybatisplus.samples.deluxe.model.UserPage;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.util.CollectionUtils;
 ​
 import javax.annotation.Resource;
 import java.util.ArrayList;
 import java.util.List;
 ​
 /**
  * @author miemie
  * @since 2018-08-13
  */
 @SpringBootTest
 class DeluxeTest {
 ​
     @Resource
     private UserMapper mapper;
 ​
     @Test
     public void testPage() {
         System.out.println("------ 自定义 xml 分页 ------");
         UserPage selectPage = new UserPage(1, 5).setSelectInt(20);
         UserPage userPage = mapper.selectUserPage(selectPage);
         Assertions.assertSame(userPage, selectPage);
         System.out.println("总条数 ------> " + userPage.getTotal());
         System.out.println("当前页数 ------> " + userPage.getCurrent());
         System.out.println("当前每页显示数 ------> " + userPage.getSize());
         print(userPage.getRecords());
 ​
         System.out.println("------ baseMapper 自带分页 ------");
         Page<User> page = new Page<>(1, 5);
         IPage<User> userIPage = mapper.selectPage(page, new QueryWrapper<User>().eq("age", 20));
         Assertions.assertSame(userIPage, page);
         System.out.println("总条数 ------> " + userIPage.getTotal());
         System.out.println("当前页数 ------> " + userIPage.getCurrent());
         System.out.println("当前每页显示数 ------> " + userIPage.getSize());
         print(userIPage.getRecords());
     }
 ​
     @Test
     void testDelAll() {
         mapper.deleteAll();
     }
 ​
     @Test
     void testInsert() {
         User u = new User().setEmail("122@qq.com").setVersion(1).setDeleted(0);
         mapper.insert(u);
 ​
         u.setAge(18);
         mapper.updateById(u);
         u = mapper.selectById(u.getId());
         // version should be updated
         Assertions.assertEquals(2, u.getVersion().intValue());
     }
 ​
     @Test
     void testSelect() {
         System.out.println(mapper.selectById(1L));
     }
 ​
     private <T> void print(List<T> list) {
         if (!CollectionUtils.isEmpty(list)) {
             list.forEach(System.out::println);
         }
     }
 ​
 ​
     @Test
     void myInsertAll() {
         long id = 1008888L;
         User u = new User().setEmail("122@qq.com").setVersion(1).setDeleted(0).setId(id);
         mapper.myInsertAll(u);
 ​
         User user = mapper.selectById(id);
         Assertions.assertNotNull(user);
         Assertions.assertNotNull(user.getCreateTime());
     }
 ​
     @Test
     void myInsertBatch() {
         long id = 1009991;
         List<User> batchList = new ArrayList<>(2);
         batchList.add(new User().setId(id++).setEmail("111@qq.com").setVersion(1).setDeleted(0));
         batchList.add(new User().setId(id).setEmail("112@qq.com").setVersion(1).setDeleted(0));
         mapper.mysqlInsertAllBatch(batchList);
 ​
         User user = mapper.selectById(1009991);
         Assertions.assertNotNull(user);
         Assertions.assertNotNull(user.getCreateTime());
     }
 ​
     @Test
     void verifyGithub1532() {
         mapper.findList(new User().setName("a")).forEach(System.out::println);
     } 
​ 
    @Test 
    void testCustomSqlSegment() { 
        QueryWrapper<User> ew = new QueryWrapper<>(); 
        ew.like("u.name", "Tom"); 
        List<User> list = mapper.customerSqlSegment(ew); 
        Assertions.assertEquals(1, list.size()); 
    } 
}
复制代码

坑点:

  • 自定义批量和自动填充功能,需要在mapper方法的参数上定义@Param(),

  • MP默认仅支持 list, collection, array 3个命名,不然无法自动填充

10、IDEA快速开发插件MyBatisX

可以实现java与xml跳转,根据Mapper接口中的方法自动生成xml结构

在plugin中搜索MyBatisX安装

posted on   zrm0612  阅读(96)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示