MyBatis-Plus
Reference:
1. MyBatisPlus简介 1.1 前言
Mybatis-Plus可以节省我们大量工作时间,所有的CRUD代码都可以自动化完成
类似的工具
JPA
Java Persistence API(Java 持久层 API):用于对象持久化的 API
作用:使得应用程序以统一的方式访问持久层
tk-mapper
实体类实心可序列化接口
1.2 MyBatis-Plus 1.2.1 简介
MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
1.2.2 特性
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小 :启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作 :内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用 :通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成 :支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式 :支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作 :支持全局通用方法注入( Write once, use anywhere )
内置代码生成器 :采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用 (自动帮你生成代码)
内置分页插件 :基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库 :支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件 :可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件 :提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
2. 基本使用
使用第三方组件的步骤:
导入对应的依赖
研究依赖如何配置
代码如何编写
提高拓展技术能力
2.1 创建数据库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 create database if not exists mybatis_plus;use mybatis_plus; 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) ); DELETE FROM user ;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' );
2.2 初始化SpringBoot项目 2.2.1 导入依赖 1 2 3 4 5 6 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.5.3.1</version > </dependency >
:warning: 引入 MyBatis-Plus
之后不要再次引入 MyBatis
和 MyBatis-Spring
,避免版本差异导致的问题
2.1.2 连接数据库 1 2 3 4 5 6 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
2.3 pojo-dao
pojo
1 2 3 4 5 6 7 8 9 @Data @NoArgsConstructor @AllArgsConstructor public class User { private int id; private String name; private int age; private String email; }
dao
1 2 3 4 public interface UserMapper extends BaseMapper <User> { }
扫描接口
主启动类添加注释,扫描dao包下的所有接口
或者在 UserMapper
上增加 @Mapper
注释
1 @MapperScan("com.bayyy.dao")
测试
1 2 3 4 5 6 7 @Autowired private UserMapper userMapper;@Test void contextLoads () { userMapper.selectList(null ).forEach(System.out::println); }
3. 配置日志
Mybatis-Plus 自动生成的 sql 语句是不可见的,需要查看日志查看相关细节
1 2 3 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4. CRUD扩展 4.1 Insert-插入 1 2 3 4 5 User user = new User ();user.setName("Bayyy" ); user.setAge(20 ); user.setEmail("110@011.com" ); int result = userMapper.insert(user);
测试结果:
4.2 update-更新 1 2 3 4 5 6 7 8 @Test void testUpdate () { User user = new User (); user.setId(1 ); user.setName("Update" ); userMapper.updateById(user); System.out.println(user); }
测试结果:
4.3 select-查询 4.3.1 单个查询 1 User user = userMapper.selectById(1 );
4.3.2 批量查询 1 List<User> users = userMapper.selectBatchIds(Arrays.asList(1 , 2 , 3 ));
4.3.3 条件查询 1 2 3 HashMap<String, Object> map = new HashMap <>(); map.put("name" , "Bayyy" ); List<User> userList = userMapper.selectByMap(map);
4.4 删除操作 4.4.1 单个删除 1 2 userMapper.deleteById(1 );
4.4.2 批量删除 1 2 userMapper.deleteBatchIds(Arrays.asList(1 , 2 ));
4.4.3 条件删除 1 2 3 4 5 HashMap<String, Object> map = new HashMap <>(); map.put("name" , "Bayyy" ); map.put("id" , 23 ); userMapper.deleteByMap(map);
4.4.4 逻辑删除
逻辑删除 | MyBatis-Plus
说明:
只对自动注入的sql起效:
插入: 不作限制
查找: 追加where条件过滤掉已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
更新: 追加where条件防止更新到已删除数据,且使用 wrapper.entity 生成的where条件会忽略该字段
删除: 转变为 更新
例如:
删除: update user set deleted=1 where id = 1 and deleted=0
查找: select id,name,deleted from user where deleted=0
字段类型支持说明:
支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)
如果数据库字段使用datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持置为函数来获取值如now()
附录:
逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
添加逻辑删除字段
pojo修改
1 2 3 @TableLogic private int deleted;
配置
1 2 3 4 5 6 mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
删除测试
1 2 userMapper.deleteById(1 );
5. 基本插件功能 5.1 主键生成 5.1.1 主键生成策略
UUID、自增ID、雪花算法、redis、Zookeeper
雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
核心思想:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
5.1.2 主键自增
1 2 3 4 5 public class User { @TableId(type = IdType.AUTO) private int id; ... }
1 2 3 4 5 6 7 public enum IdType { AUTO(0 ), NONE(1 ), INPUT(2 ), ASSIGN_ID(3 ), ASSIGN_UUID(4 ); }
5.2 自动填充 5.2.1 数据库级别的修改
表中新增字段 create_time
、update_time
测试插入和更新操作
5.2.2 代码级别
数据库其中的默认值等删除
实体类字段属性上需要增加注解
1 2 3 4 5 6 7 @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
编写处理器对注解进行处理
Doucment:自动填充功能 | MyBatis-Plus
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @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()); this .strictInsertFill(metaObject, "updateTime" , LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill (MetaObject metaObject) { log.info("start update fill..." ); this .strictUpdateFill(metaObject, "updateTime" , LocalDateTime.class, LocalDateTime.now()); } }
测试结果:
5.3 乐观锁 5.3.1 简介
乐观锁 OptimisticLockerInnerInterceptor
顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁!如果出现问题就再次更新测试
步骤:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
5.3.2 测试
说明:
支持的数据类型只有:int, Integer, long, Long, Date, Timestamp, LocalDateTime
整数类型下 newVersion = oldVersion + 1
newVersion
会回写到 entity
中
仅支持 updateById(id)
与 update(entity, wrapper)
方法
在 update(entity, wrapper)
方法下, wrapper
不能复用!!!
数据库增加 version
字段
pojo增加对应字段
1 2 3 @Version private int version;
注册组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration @EnableTransactionManagement @MapperScan("com.bayyy.dao") public class MybatisPlusConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor () { return new OptimisticLockerInterceptor (); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor (); mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor ()); return mybatisPlusInterceptor; } }
测试
修改的用户不能为 new
出的新对象,必须为查询出来的结果
1 2 3 4 5 6 7 @Test void testUpdate () { User user = userMapper.selectById(1 ); user.setName("Bayyy_version" ); userMapper.updateById(user); System.out.println(user); }
测试结果:
5.4 分页查询
注册组件
官网配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Configuration @MapperScan("scan.your.mapper.package") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); interceptor.addInnerInterceptor(new PaginationInnerInterceptor (DbType.H2)); return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer () { return configuration -> configuration.setUseDeprecatedExecutor(false ); } }
最新版中,所有配置都放在同一类下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor (); mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor ()); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor (DbType.MYSQL)); return mybatisPlusInterceptor; }
测试
class Page()
该类继承了 IPage
类,实现了 简单分页模型
如果要实现自己的分页模型,可以继承 Page
类或者实现 IPage
类
属性名
类型
默认值
描述
records
List
emptyList
查询数据列表
total
Long
0
查询列表总记录数
size
Long
10
每页显示条数,默认 10
current
Long
1
当前页
orders
List
emptyList
排序字段信息,允许前端传入的时候,注意 SQL 注入问题,可以使用 SqlInjectionUtils.check(...)
检查文本
optimizeCountSql
boolean
true
自动优化 COUNT SQL 如果遇到 jSqlParser
无法解析情况,设置该参数为 false
optimizeJoinOfCountSql
boolean
true
自动优化 COUNT SQL 是否把 join 查询部分移除
searchCount
boolean
true
是否进行 count 查询,如果指向查询到列表不要查询总记录数,设置该参数为 false
maxLimit
Long
单页分页条数限制
countId
String
xml
自定义 count
查询的 statementId
也可以不用指定在分页 statementId
后面加上 _mpCount
例如分页 selectPageById
指定 count 的查询 statementId
设置为 selectPageById_mpCount
即可默认找到该 SQL
执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Page () {}public Page (long current, long size) {}public Page (long current, long size, long total) {}public Page (long current, long size, boolean searchCount) {}public Page (long current, long size, long total, boolean searchCount) {}
1 2 3 4 5 6 7 @Test void testPage () { Page<User> page = new Page <>(1 , 5 ); userMapper.selectPage(page, null ); List<User> userList = page.getRecords(); userList.forEach(System.out::println); }
测试结果:
6. 性能分析-p6spy
导入依赖
1 2 3 4 5 6 <dependency > <groupId > p6spy</groupId > <artifactId > p6spy</artifactId > <version > 3.9.1</version > </dependency >
springboot配置
1 2 3 4 5 6 spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC username: root password: 123456
spy配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 modulelist =com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory logMessageFormat =com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger appender =com.baomidou.mybatisplus.extension.p6spy.StdoutLogger deregisterdrivers =true useprefix =true excludecategories =info,debug,result,commit,resultset dateformat =yyyy-MM-dd HH:mm:ss outagedetection =true outagedetectioninterval =2
测试
7. 条件构造器-wrapper
条件构造器 | MyBatis-Plus
7.1 比较
非空 isNotNull
空 isNull
相等 eq
不等于 ne
大于 gt
大于等于 ge
小于 lt
小于等于 le
区间 between
1 2 3 4 5 6 QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.ge("age" , 20 ) .lt("age" , 100 ) .isNotNull("name" ) .isNull("email" ); userMapper.selectList(wrapper).forEach(System.out::println);
测试结果:
7.2 模糊查询
1 2 3 4 5 QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.likeLeft("age" , 2 ) .in("name" , new String []{"Bayyy" , "Bayyy2" }) .notIn("id" , new Integer []{1 , 2 , 3 }); userMapper.selectList(wrapper).forEach(System.out::println);
测试结果:
7.3 联表查询
inSql
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
1 2 3 4 QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.inSql("id" , "select id from user where id < 30" ) .notInSql("age" , "select age from user where age < 30 and age > 3" ); userMapper.selectList(wrapper).forEach(System.out::println);
测试结果:
7.4 分组/排序
groupBy
- groupBy("id", "name")
—->group by id,name
orderByAsc
- orderByAsc("id", "name")
—->order by id ASC,name ASC
orderByDesc
- orderByDesc("id", "name")
—->order by id DESC,name DESC
orderBy
- orderBy(boolean condition, boolean isAsc, R... columns)
8. 代码自动生成器
代码生成器配置新 | MyBatis-Plus
导入依赖
1 2 3 4 5 6 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-generator</artifactId > <version > 3.5.3</version > </dependency >
1 2 3 4 5 <dependency > <groupId > org.apache.velocity</groupId > <artifactId > velocity-engine-core</artifactId > <version > 2.2</version > </dependency >
编写生成器代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public static void main (String[] args) { FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC" , "root" , "123456" ) .globalConfig(builder -> { builder.author("bayyy" ) .disableOpenDir() .outputDir(System.getProperty("user.dir" ) + "/src/main/java" ) .enableSwagger() .dateType(DateType.ONLY_DATE) .commentDate("yyyy-MM-dd" ); }) .packageConfig(builder -> { builder.parent("com.bayyy" ) .moduleName("mybatis_plus" ) .entity("pojo" ) .service("service" ) .serviceImpl("service.impl" ) .mapper("dao" ) .xml("dao.mapper" ) .controller("controller" ) .pathInfo(null ); }) .strategyConfig(builder -> { builder.addInclude("user" ) .addTablePrefix("t_" ) .addFieldSuffix("_flag" ) .enableCapitalMode() .entityBuilder() .enableLombok() .columnNaming(NamingStrategy.underline_to_camel) .naming(NamingStrategy.underline_to_camel) .addTableFills(new Column ("update_time" , FieldFill.INSERT)) .addTableFills(new Column ("create_time" , FieldFill.INSERT_UPDATE)) .versionColumnName("version" ) .idType(IdType.AUTO) .logicDeleteColumnName("deleted" ) .serviceBuilder() .enableFileOverride() .formatServiceImplFileName("%sServiceImpl" ) .formatServiceFileName("%sService" ) .controllerBuilder() .enableRestStyle() .enableHyphenStyle() .build(); }) .execute(); } }
测试结果: