跟随尚硅谷MyBatisPlus教程整理
1、简介
MyBatis的增强工具包(简称:MP),只做增强,不做改变,为简化工作、提高生产效率而生
官方地址:
代码发布地址:
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/
前置知识:
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');
插入数据,创建这样一个表
id | name | age | |
---|---|---|---|
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
2.5、编写Mapper类
继承BaseMapper,泛型加入之前写的实体类
public interface UserMapper extends BaseMapper<User> {
}
2.6、启动类
在 Spring Boot 启动类中添加 @MapperScan
注解,扫描 Mapper 文件夹(Mapper对应路径):
在测试类中可以直接调用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(
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
则在map
的value
为null
时调用
例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
: 过滤函数,是否允许字段传入比对条件中params
与null2IsNull
: 同上
例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数据库为例
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#query
和 Executor#update
和 StatementHandler#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>
测试文件
发现是删除全表操作,立即终止
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)
说明:
支持的数据类型只有: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
该项目演示以下功能:
-
逻辑删除
-
自动填充
-
自定义全局方法: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
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结构
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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语句:使用策略模式优化代码结构