1:Mybatis-Plus简介
官网文档
MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性:
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小 :启动即会自动注入基本 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:快速开始
本模块来自官方文档,使用SpringBoot完成
2-1:创建表和添加数据 User
表结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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' );
2-2:初始化工程 SpringBoot版本:2.3.3.RELEASE Mybatis-Plus:3.3.2 MySQL:8.21
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 25 26 27 28 29 30 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.3.2</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > </exclusion > </exclusions > </dependency >
如上,mp只需要添加一个依赖,其余都是常规的依赖。
2-2-2:配置 配置数据库连接:
1 2 3 4 spring.datasource.username =root spring.datasource.password =123456 spring.datasource.url =jdbc:mysql://localhost:3306/practice?userUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
日志查看sql语句(可以不加):
1 2 3 4 5 mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹(可在mapper.java文件上使用@Mapper
代替):
1 2 3 4 5 6 7 8 9 @MapperScan("com.xqm.mybatisplus.mapper") @SpringBootApplication public class MybatisPlusApplication { public static void main (String[] args) { SpringApplication.run(MybatisPlusApplication.class, args); } }
2-2-3:编码 实例类User.java
(此处使用lombok )
1 2 3 4 5 6 7 8 9 @Data @Accessors(chain = true) public class User { private Long id; private String name; private Integer age; private String email; }
接口文件mapper.java
1 2 3 4 5 @Repository public interface UserMapper extends BaseMapper <User > {}
2-2-4:开始使用 1 2 3 4 5 6 7 8 9 10 11 12 @SpringBootTest class MybatisPlusApplicationTests { @Autowired UserMapper userMapper; @Test void contextLoads () { List<User> users = userMapper.selectList(null ); users.forEach(System.out::println); } }
UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件
控制台输出:
1 2 3 4 5 User(id=1 , name=Jone, age=18 , email=test1@baomidou .com) User(id=2 , name=Jack, age=20 , email=test2@baomidou .com) User(id=3 , name=Tom, age=28 , email=test3@baomidou .com) User(id=4 , name=Sandy, age=21 , email=test4@baomidou .com) User(id=5 , name=Billie, age=24 , email=test5@baomidou .com)
3:CRUD扩展 3-1:插入操作 1 2 3 4 5 6 7 8 9 10 @Test void testInsert () { User user = new User(); user.setName("失铭" ).setAge(1 ).setEmail("123@qq.com" ); int insert = userMapper.insert(user); System.out.println(insert); System.out.println(user); }
3-2:主键策略
@TableId
:主键注解,添加后默认:ASSIGN_UUID 分配UUID
属性
类型
必须指定
默认值
描述
value
String
否
“”
主键字段名
type
Enum
否
IdType.ASSIGN_UUID
主键类型
3-2-1:IdType
值
描述
AUTO
数据库ID自增
NONE
无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT
insert前自行set主键值
ASSIGN_ID
分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID
分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)
ID_WORKER
分布式全局唯一ID 长整型类型(please use ASSIGN_ID)
UUID
32位UUID字符串(please use ASSIGN_UUID)
ID_WORKER_STR
分布式全局唯一ID 字符串类型(please use ASSIGN_ID)
雪花算法 :snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
type设置为IdType.INPUT,需要手动输入,否则id为null
3-2-2:主键自增
实体类字段上``@TableId(type=IdType.AUTO)
数据库主键设置自增
3-3:更新操作 1 2 3 4 5 6 7 8 9 10 11 @Test void testUpdate () { User user = new User(); user.setId(1L ).setAge(10 ).setEmail("123@gmail.com" ); int i = userMapper.updateById(user); System.out.println(i); }
3-4:自动填充 创建时间(gmt_create),修改时间(gmt_modified)。自动化完成,不希望手动更新! 阿里巴巴java开发手册中规定如下:
3-4-1:方式一:数据库级别
数据类型设为:datatime
默认值:CURRENT_TIMESTAMP
注意:更新时间需要设置为根据时间戳更新
添加字段:
1 2 private Date gmtCreate;private Date gmtModified;
3-4-2:方式二:代码级别 添加注解
1 2 3 4 5 @TableField(fill = FieldFill.INSERT) private LocalDateTime gmtCreate;@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime gmtModified;
注意:数据类型使用LocalDateTime,这里有一个小坑,等会说。
自定义实现类MyMetaObjectHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { log.info("start insert fill ...." ); this .strictInsertFill(metaObject, "gmtCreate" , LocalDateTime.class, LocalDateTime.now()); this .strictInsertFill(metaObject, "gmtModified" , LocalDateTime.class, LocalDateTime.now()); } @Override public void updateFill (MetaObject metaObject) { log.info("start update fill ...." ); this .strictUpdateFill(metaObject, "gmtModified" , LocalDateTime.class, LocalDateTime.now()); } }
重新测试插入和更新,可以看到时间填充成功。
这里有两个问题:
时间差8个小时 :这是因为数据库连接语句时区的设置的世界标准时间(UTC),而北京时间比标准时间快八个小时。所以需要将时间设置为:GMT%2B8
。如:serverTimezone=GMT%2B8
LocalDateTime :LocalDateTime
是java8提供的新的日期和时间API,详细了解参考->廖雪峰的官方网站-LocalDateTime 。实体类和实现类的时间类型要一致。例如:我的gmtCreate
使用的LocalDateTime
类型,那么填充语句中,也要写LocalDateTime.class
。实体类定义的是Date
类型,填充语句就要写Date.class
。
3-4-3:@TableField
注解(了解) @TableField
:字段注解(非主键)
属性
类型
必须指定
默认值
描述
value
String
否
“”
数据库字段名
el
String
否
“”
映射为原生 #{ ... }
逻辑,相当于写在 xml 里的 #{ ... }
部分
exist
boolean
否
true
是否为数据库表字段
condition
String
否
“”
字段 wher
e 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s}
,参考
update
String
否
“”
字段 update set
部分注入, 例如:update="%s+1"
:表示更新时会set version=version+1(该属性优先级高于 el 属性)
insertStrategy
Enum
N
DEFAULT
举例:NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
updateStrategy
Enum
N
DEFAULT
举例:IGNORED: update table_a set column=#
{columnProperty}
whereStrategy
Enum
N
DEFAULT
举例:NOT_EMPTY: where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
fill
Enum
否
FieldFill.DEFAULT
字段自动填充策略
select
boolean
否
true
是否进行 select 查询
keepGlobalFormat
boolean
否
false
是否保持使用全局的 format 进行处理
jdbcType
JdbcType
否
JdbcType.UNDEFINED
JDBC类型 (该默认值不代表会按照该值生效)
typeHandler
Class<? extends TypeHandler>
否
UnknownTypeHandler.class
类型处理器 (该默认值不代表会按照该值生效)
numericScale
String
否
“”
指定小数点后保留的位数
关于jdbcType
和typeHandler
以及numericScale
的说明 : numericScale只生效于 update 的sql. jdbcType和typeHandler如果不配合@TableName#autoResultMap = true一起使用,也只生效于 update 的sql. 对于typeHandler如果你的字段类型和set进去的类型为equals关系,则只需要让你的typeHandler让Mybatis加载到即可,不需要使用注解
FieldStrategy:
值
描述
IGNORED
忽略判断
NOT_NULL
非NULL判断
NOT_EMPTY
非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
DEFAULT
追随全局配置
FieldFill:
值
描述
DEFAULT
默认不处理
INSERT
插入时填充字段
UPDATE
更新时填充字段
INSERT_UPDATE
插入和更新时填充字段
4:乐观锁
悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
乐观锁实现只需两步
4-1. 注册组件OptimisticLockerInterceptor 1 2 3 4 @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor () { return new OptimisticLockerInterceptor(); }
4-2.:version字段添加@Version注解 1 2 @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 不能复用!!!
4-3:乐观锁示例 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 @Test void testOptimisticLocker () { User user = userMapper.selectById(5L ); user.setEmail("456@gmail.com" ).setAge(100 ); userMapper.updateById(user); } @Test void testOptimisticLocker2 () { User user = userMapper.selectById(5L ); user.setEmail("456@gmail.com" ).setAge(100 ); User user2 = userMapper.selectById(5L ); user2.setEmail("123456@gmail.com" ).setAge(50 ); userMapper.updateById(user2); userMapper.updateById(user); }
观察控制台日志,可以发现:
1 UPDATE user SET name =?, age=?, email=?, version =?, gmt_create=?, gmt_modified=? WHERE id =? AND version =?
如上,id和version都相同时才更新,很简单的就实现了乐观锁
5:查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test void testSelect () { User user = userMapper.selectById(1L ); System.out.println(user); } @Test void testSelectBatchIds () { List<User> users = userMapper.selectBatchIds(Arrays.asList(1 , 2 , 3 )); System.out.println(users); } @Test void testSelectByMap () { HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("name" , "Jone" ); List<User> users = userMapper.selectByMap(hashMap); System.out.println(users); }
5-1:分页查询 5-1-1:添加插件 1 2 3 4 5 6 7 8 9 10 11 @Bean public PaginationInterceptor paginationInterceptor () { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true )); return paginationInterceptor; }
也可以简单一点:
1 2 3 4 @Bean public PaginationInterceptor paginationInterceptor () { return new PaginationInterceptor(); }
5-2-2:分页查询示例 1 2 3 4 5 6 7 8 9 10 11 @Test void testPage () { Page<User> page = new Page<>(1 , 5 ); userMapper.selectPage(page, null ); page.getRecords().forEach(System.out::println); }
6:删除操作 1 2 3 4 5 6 7 8 9 10 11 @Test void testDelete () { userMapper.deleteById(1297730862471913474L ); userMapper.deleteBatchIds(Arrays.asList(1297730992902164482L , 1297731359643713538L )); HashMap<String, Object> hashMap = new HashMap<>(); hashMap.put("id" ,1297731433178230785L ); userMapper.deleteByMap(hashMap); }
6-1:逻辑删除
物理删除:从数据库中直接移除
逻辑删除:未从数据库中移除,而是通过一个变量让他失效,例:deleted = 0 -> deleted =1
准备工作:
数据库添加字段:
默认值为0
修改实体类:
实际使用:
修改配置文件:
1 2 3 4 5 6 7 mybatis-plus.global-config.db-config.logic-delete-field =deleted mybatis-plus.global-config.db-config.logic-delete-value =1 mybatis-plus.global-config.db-config.logic-not-delete-value =0
实体类添加@TableLogic
注解:
如果设置了mybatis-plus.global-config.db-config.logic-delete-field = deleted
,那么只需要有deleted这个属性即可,不需要添加注解。
1 2 @TableLogic private Integer deleted;
修改配置后原来的删除语句就会变为更新语句,例:
1 2 3 4 5 6 @Test void testDelete () { userMapper.deleteById(2L ); }
注:添加逻辑删除后查询自动添加deleted = 0
条件
官方说明如下:
7:条件构造器 十分重要:Wrapper 我们写一些复杂的sql就可以使用它来替代!
官方文档
1、测试一
1 2 3 4 5 6 7 8 9 10 11 @Test void contextLoads () { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .isNotNull("name" ) .isNotNull("email" ) .ge("age" ,12 ); userMapper.selectList(wrapper).forEach(System.out::println); }
2、测试二
1 2 3 4 5 6 7 8 @Test void test2 () { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.eq("name" ,"狂神说" ); User user = userMapper.selectOne(wrapper); System.out.println(user); }
3、测试三
1 2 3 4 5 6 @Test void test3 () { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.between("age" ,20 ,30 ); Integer count = userMapper.selectCount(wrapper); }
4、测试四
1 2 3 4 5 6 7 8 9 10 @Test void test4 () { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .notLike("name" ,"e" ) .likeRight("email" ,"t" ); List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
5、测试五
1 2 3 4 5 6 7 8 @Test void test5 () { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.inSql("id" ,"select id from user where id<3" ); List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }
6、测试六
1 2 3 4 5 6 7 @Test void test6 () { QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.orderByAsc("id" ); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
8:自动生成代码 依赖:
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-generator</artifactId > <version > 3.4.0</version > </dependency > <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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.DataSourceConfig;import com.baomidou.mybatisplus.generator.config.GlobalConfig;import com.baomidou.mybatisplus.generator.config.PackageConfig;import com.baomidou.mybatisplus.generator.config.StrategyConfig;import com.baomidou.mybatisplus.generator.config.po.TableFill;import com.baomidou.mybatisplus.generator.config.rules.DateType;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import java.util.ArrayList;public class MybatisGenerator { public static void Generator (String moduleName, String include) { AutoGenerator mpg = new AutoGenerator(); GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir" ); gc.setOutputDir(projectPath + "/src/main/java" ); gc.setAuthor("ShiMing" ); gc.setOpen(false ); gc.setFileOverride(true ); gc.setServiceName("%sService" ); gc.setIdType(IdType.ASSIGN_ID); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true ); mpg.setGlobalConfig(gc); DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/practice?userUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8" ); dsc.setDriverName("com.mysql.cj.jdbc.Driver" ); dsc.setUsername("root" ); dsc.setPassword("123456" ); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); PackageConfig pc = new PackageConfig(); pc.setModuleName(moduleName); pc.setParent("com.xqm" ); pc.setEntity("pojo" ); pc.setMapper("mapper" ); pc.setService("service" ); pc.setController("controller" ); mpg.setPackageInfo(pc); StrategyConfig strategy = new StrategyConfig(); strategy.setInclude(include.split("," )); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true ); strategy.setChainModel(true ); TableFill gmtCreate = new TableFill("gmt_create" , FieldFill.INSERT); TableFill gmtModified = new TableFill("gmt_modified" , FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills = new ArrayList<>(); tableFills.add(gmtCreate); tableFills.add(gmtModified); strategy.setTableFillList(tableFills); strategy.setLogicDeleteFieldName("deleted" ); strategy.setVersionFieldName("version" ); strategy.setRestControllerStyle(true ); strategy.setControllerMappingHyphenStyle(true ); mpg.setStrategy(strategy); mpg.execute(); } public static void main (String[] args) { String moduleName = "mybatis" ; String include = "user,student" ; Generator(moduleName, include); } }
使用效果如下:
9:参考