封面画师:T5-茨舞(微博) 封面ID:69983911
1. MyBatis-Plus概述
1.1 为什么要学习?
Hibernate是一种全自动化的ORM框架,但是MyBatis不是,人们认为MyBatis属于半自动化的ORM框架,因为需要手写SQL语句。针对复杂的SQL进行手写倒也还好,但是一些简单的SQL还要手写就变得十分麻烦(我知道有注解,闭嘴!),同时还会让XML映射文件变得十分臃肿,那么有没有方法可以省略简单SQL的书写呢?
这时候,一个“天降猛男”登场了——MyBatis-Plus。💪
MyBatis-Plus可以很好地帮我们简化操作,对于一些简单的CRUD操作,不再需要我们编写。当然,除了这个最“肤浅”的作用外,MyBatis-Plus还有很多其他的特性,总之就是一句话:为了简化操作,为了偷懒。😂
MyBatis-Plus由国人开发,文档很详细,编码符合国人习惯,荣获【2019年度开源中国最受欢迎软件,开发工具类 TOP1】,还能简化开发,所以…不学一个?
官方文档:官网链接1 或者 官网链接2
PS:本文中,MP是MyBatis-Plus的简写。
2.2 MyBatis-Plus概述
官网:MyBatis-Plus
MyBatis-Plus (简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
基友搭配,效率翻倍! 😆
1.3 特性
无侵入 :只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小 :启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作 :内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 ( 言外之意,简单的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. 快速开始
官方快速开始地址:MyBatis-Plus-QuickStart
拓展使用第三方组件的步骤:
导入对应的依赖
编写对应的配置
研究代码的编写
提高技术的拓展
步骤
1、创建一个数据库mybatis_plus
2、创建user表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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' );
3、使用IDEA创建一个SpringBoot项目mybatis_plus
,选择web和lombok依赖进行导入。
4、导入其他依赖
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > Latest Version</version > </dependency >
说明:使用MyBatis-Plus可以节省代码的编写,尽量 不要同时 导入MyBatis-Plus和MyBatis,避免存在依赖错误。
5、连接数据库
1 2 3 4 spring.datasource.username =root spring.datasource.password =123456 spring.datasource.url =jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver
没使用MyBatis-Plus时,我们需要编写pojo、dao、service、controller,但是使用了Mybatis-Plus后,就不需要这么麻烦了。
6、使用Mybatis-Plus后:
1 2 3 4 5 6 7 8 9 10 @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
1 2 3 4 5 @Repository public interface UserMapper extends BaseMapper <User> { }
注意:编写完mapper接口后,还需要在主启动类上扫描mapper文件夹: @MapperScan("com.yang.mapper")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @SpringBootTest class MybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads () { List<User> users = userMapper.selectList(null ); users.forEach(System.out::println); } }
查询结果:
思考
我们没有编写SQL,是怎么查询出来的? MyBatis-Plus已经帮我们写好了
SQL操作的方法又是拿来的? MyBatis-Plus已经帮我们写好了
3. 配置日志
使用MyBatis-Plus后,部分SQL是不可见的,我们希望知道它是如何执行的,这个时候就需要查看日志!
在配置文件中进行配置:
1 2 3 mybatis-plus.configuration.log-impl =org.apache.ibatis.logging.stdout.StdOutImpl
配置日志后,在以后的学习中,我们可以查看日志,观察MyBatis-Plus的SQL执行。
4. 数据基本操作
所有操作,均在测试类中进行。
4.1 数据插入
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testInsert () { User user = new User (); user.setName("默烦" ); user.setAge(18 ); user.setEmail("cy.mofan@qq.com" ); int result = userMapper.insert(user); System.out.println(result); System.out.println(user); }
然后运行测试:
我们发现:我们明明没有设置ID,插入时居然自动生成了ID,而且生成的ID似乎有点“ 奇怪 ”。
数据库插入的ID应该是全局唯一的,这个时候我们需要了解一些主键生成策略。👇
4.2 主键生成策略
可以使用UUID、自增ID、雪花算法、Redis、ZK等方式来生成主键。
默认 ID_WORKER 全局唯一ID
参考链接:分布式系统唯一ID生成方案汇总
MyBatis-Plus默认采用的使用推特的雪花算法
。
雪花算法 :
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。
我们可以前往我们的User
实体类,然后在id属性上加一个注解:@TableId
,点击进入这个注解:
1 2 3 4 5 6 7 8 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface TableId { String value () default "" ; IdType type () default IdType.NONE; }
点击并进入Idtype
:
1 2 3 4 5 6 7 8 9 10 11 12 public enum IdType { AUTO(0 ), NONE(1 ), INPUT(2 ), ID_WORKER(3 ), UUID(4 ), ID_WORKER_STR(5 ); private int key; private IdType (int key) {this .key = key;} public int getKey () {return this .key;} }
我们发现这是一个枚举类,其中的ID_WORKER
就是默认生成策略。
主键自增
使用MyBatis-Plus,设置组件生成策略为 自增 的方法:
1、实体类中表示组件的属性上添加注解:@TableId(type = IdType.AUTO)
2、 数据库字段一定要是自增的
3、注解添加完成、数据库字段修改(记得保存)都完成后,我们在此运行插入测试:
手动输入
使用MyBatis-Plus,设置组件生成策略为 手动输入 的方法:
只需要在实体类中表示组件的属性上添加注解:@TableId(type = IdType.AUTO)
即可。
如果我们设置组件生成策略为 手动输入 ,但是没有输入,这个时候日志就会显示插入的是null
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testInsert () { User user = new User (); user.setId(6L ); user.setName("mofan" ); user.setAge(18 ); user.setEmail("cy.mofan@qq.com" ); int result = userMapper.insert(user); System.out.println(result); System.out.println(user); }
运行结果:
4.3 数据更新
更新单个数据
根据ID更新单个数据:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testUpdate () { User user = new User (); user.setId(6L ); user.setName("Yang" ); int result = userMapper.updateById(user); System.out.println(result); System.out.println(user); }
控制台日志打印:
数据库显示:
更新多个数据
根据ID更新多个数据:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testUpdate () { User user = new User (); user.setId(6L ); user.setName("mofan" ); user.setAge(20 ); int result = userMapper.updateById(user); System.out.println(result); System.out.println(user); }
控制台日志打印:
数据库显示:
MP(以下 MyBatis-Plus 简写为 MP)会自动根据给对象设置的属性值来更新数据。
更新字段值为 null
有两种可供方式可供参考:
使用 LambdaUpdateWrapper
使用 UpdateWrapper
现有如下的实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Getter @Setter @TableName("tb_test_null") public class TestNull extends BaseEntity { @Serial private static final long serialVersionUID = -1332169884425770306L ; @TableField("name") private String name; @TableField("age") private Integer age; @TableField("empty_field") private String emptyField; }
先插入一条数据:
1 2 3 4 5 6 7 8 9 10 private static final String UNIQUE_NAME = "mofan" ;@BeforeEach public void init () { TestNull testNull = new TestNull (); testNull.setName(UNIQUE_NAME); testNull.setAge(21 ); testNull.setEmptyField("" ); testNullDao.insert(testNull); }
然后尝试将 age
和 emptyField
值更新为 null
:
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 private List<TestNull> getMofanTestNull () { LambdaQueryWrapper<TestNull> wrapper = Wrappers.lambdaQuery(TestNull.class) .eq(TestNull::getName, UNIQUE_NAME); return testNullDao.selectList(wrapper); } @Test public void testLambdaWrapperUpdateNull () { Optional<TestNull> optional = getMofanTestNull().stream() .filter(i -> UNIQUE_NAME.equals(i.getName())) .findFirst(); var wrapper = Wrappers.lambdaUpdate(TestNull.class) .set(TestNull::getEmptyField, null ) .set(TestNull::getAge, null ); optional.ifPresent(i -> testNullDao.update(i, wrapper)); optional = getMofanTestNull().stream() .filter(i -> UNIQUE_NAME.equals(i.getName())) .findFirst(); optional.ifPresent( entity -> assertThat(entity) .extracting(TestNull::getEmptyField, TestNull::getAge) .containsOnlyNulls() ); } @Test public void testWrapperUpdateNull () { Optional<TestNull> optional = getMofanTestNull().stream() .filter(i -> UNIQUE_NAME.equals(i.getName())) .findFirst(); if (optional.isEmpty()) { Assertions.fail(); } TestNull entity = optional.get(); var wrapper = new UpdateWrapper <>(entity); wrapper.set("empty_field" , null ).set("age" , null ); testNullDao.update(entity, wrapper); optional = getMofanTestNull().stream() .filter(i -> UNIQUE_NAME.equals(i.getName())) .findFirst(); optional.ifPresent( i -> assertThat(i) .extracting(TestNull::getEmptyField, TestNull::getAge) .containsOnlyNulls() ); }
4.4 自动填充
创建时间、更新时间,对于这两个字段的操作我们希望是自动完成而不是需要手动编写。
阿里巴巴开发手册说过:任何数据库表都必须有两个字段:gmt_created(创建时间)、gmt_modified(修改时间),而且需要这两个字段的更新自动化。
自动填充有两种方式:数据库级别、代码级别
数据库级别(实际开发不允许)
1、在表中新增两个字段create_time
、update_time
,并将这两个字段类型设置为timestamp
,开启根据当前时间戳更新:
2、同步实体类:
1 2 3 private Date createTime;private Date updateTime;
3、执行更新测试操作:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testUpdate () { User user = new User (); user.setId(6L ); user.setName("mofan" ); user.setAge(19 ); int result = userMapper.updateById(user); System.out.println(result); System.out.println(user); }
4、查看结果:
代码级别
1、由于前面的测试,我们需要移除数据库的默认值和更新操作:
2、实体类的属性上增加注解
1 2 3 4 5 @TableField(fill = FieldFill.INSERT) private Date createTime;@TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
3、编写处理器来处理这些注解
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("开始进行插入..." ); this .setFieldValByName("createTime" ,new Date (),metaObject); this .setFieldValByName("updateTime" ,new Date (),metaObject); } @Override public void updateFill (MetaObject metaObject) { log.info("开始进行更新..." ); this .setFieldValByName("updateTime" ,new Date (),metaObject); } }
4、测试插入:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testInsert () { User user = new User (); user.setName("mofan" ); user.setAge(17 ); user.setEmail("cy.mofan@qq.com" ); int result = userMapper.insert(user); System.out.println(result); System.out.println(user); }
5、观察插入数据:
4.5 乐观锁
与乐观锁相对的有:悲观锁
乐观锁:一个“乐观”的锁,它总是认为不会出问题,无论干什么都不会加锁。如果出了问题,再次更新值测试
悲观锁:一个“悲观”的锁,它总是认为会出现问题,无论干什么都会加上锁,然后再去操作!
在这主要讲解 乐观锁 机制。
乐观锁插件的适用场景
官方解释:乐观锁插件
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败
1 2 3 4 5 update user set name = "mofan", version = version + 1 where id = 6 and version = 1
测试MP的乐观锁插件
1、给数据库增加version
字段,并设置默认值为1
2、给实体类增加相应的字段,并添加注解@Version
1 2 @Version private Integer version;
3、注册组件
1 2 3 4 5 6 7 8 9 10 11 12 @EnableTransactionManagement @Configuration @MapperScan("com.yang.mapper") public class MyBatisPlusConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor () { return new OptimisticLockerInterceptor (); } }
4、编写测试方法进行测试:
1 2 3 4 5 6 7 8 9 10 11 @Test public void testOptimisticLocker () { User user = userMapper.selectById(1l ); user.setName("mofan" ); user.setEmail("cy.mofan@qq.com" ); userMapper.updateById(user); }
数据库结果:
我们再来测试一下修改失败的情况,主要模拟多线程情况下线程插队的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testOptimisticLocker2 () { User user = userMapper.selectById(1l ); user.setName("mofan111" ); user.setEmail("cy.mofan@qq.com" ); User user2 = userMapper.selectById(1l ); user2.setName("mofan222" ); user2.setEmail("cy.mofan@qq.com" ); userMapper.updateById(user2); userMapper.updateById(user); }
如果没有乐观锁,最后一次的修改会覆盖前一次的修改,但是有了乐观锁就不会出现这种情况了。查看数据库修改结果:
查看数据库后,结果与我们预期一样。
4.6 数据查询
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 @Test public void testSelectById () { User user = userMapper.selectById(1l ); System.out.println(user); } @Test public void testSelectBatchId () { List<User> users = userMapper.selectBatchIds(Arrays.asList(1 , 2 , 3 )); users.forEach(System.out::println); } @Test public void testSelectByBatchIds () { HashMap<String, Object> map = new HashMap <>(); map.put("name" ,"默烦" ); map.put("age" ,"18" ); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }
4.7 数据分页查询
分页查询的方法有很多,比如:
1、原始的 limit
进行分页
2、第三方插件进行分页,比如:PageHelper
3、MP内置的分页插件
使用MP内置的分页插件
1、配置拦截器组件
官方配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @EnableTransactionManagement @Configuration @MapperScan("com.baomidou.cloud.service.*.mapper*") public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor () { PaginationInterceptor paginationInterceptor = new PaginationInterceptor (); paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize (true )); return paginationInterceptor; } }
我测试用的配置:
1 2 3 4 5 6 7 8 9 10 @Bean public PaginationInterceptor paginationInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); final PaginationInnerInterceptor innerInterceptor = new PaginationInnerInterceptor (DbType.MYSQL); innerInterceptor.setMaxLimit(150L ); interceptor.addInnerInterceptor(innerInterceptor); return interceptor; }
2、直接使用Page对象即可
1 2 3 4 5 6 7 8 9 @Test public void testPage () { Page<User> page = new Page <>(2 ,5 ); userMapper.selectPage(page, null ); System.out.println("总记录数为:" +page.getTotal()); page.getRecords().forEach(System.out::println); }
控制台输出:
数据库数据对比:
4.8 数据删除
三种基本的删除:删除单个数据、删除多个数据、根据条件删除。
删除单个数据
1 2 3 4 5 @Test public void testDeleteById () { userMapper.deleteById(1272430143247347718L ); }
删除数据前的数据库:参考分页查询最后一张数据库示例图。
删除数据后的数据库:
删除多个数据
1 2 3 4 5 @Test public void testDeleteBatchById () { userMapper.deleteBatchIds(Arrays.asList(1272430143247347715L ,1272430143247347716L )); }
删除数据后的数据库:
根据条件删除
1 2 3 4 5 6 7 @Test public void testDeleteMap () { HashMap<String, Object> map = new HashMap <>(); map.put("name" ,"默烦" ); userMapper.deleteByMap(map); }
删除数据后的数据库:
4.9 逻辑删除
物理删除:从数据库中直接移除数据。
逻辑删除:数据没有从数据库中移除,只是打了个标志让用户看不到。(学过HBase可以参考HBase的删除逻辑)
应用场景:管理员可以查看被删除的数据。
测试逻辑删除
1、在数据表中增加逻辑删除字段delete
,默认值为0,表示未被删除。
如果在数据库级别没有设置默认值,那么需要添加自动填充,如:
1 2 3 4 5 @Override public void insertFill (MetaObject metaObject) { ... this .setFieldValByName("deleted" , 0 , metaObject); }
2、实体类中增加属性,并添加注解@TableLogic
1 2 3 4 @TableLogic private Integer deleted;
3、编写配置文件
1 2 3 4 5 6 7 mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0
4、进行测试
1 2 3 4 @Test public void testDeleteById () { userMapper.deleteById(1L ); }
控制台输出信息:
数据表信息:
我们在进行查询,看看能够查询到被逻辑删除的数据:
1 2 3 4 5 @Test public void testSelectById () { User user = userMapper.selectById(1l ); System.out.println(user); }
控制台打印信息:
我们发现,引进逻辑删除后再进行查询会自动过滤被逻辑删除的数据。
5. 条件构造器
官方参考文档:条件构造器
5.1 QueryWrapper
我们写一些复杂的SQL时,就可以用它来替代!
前置:创建一个测试类WrapperTest
:
1 2 3 4 5 6 7 @SpringBootTest public class WrapperTest { @Autowired private UserMapper userMapper; }
当前数据表数据:
查询name不为空、email不为空、年龄大于等于12的用户
1 2 3 4 5 6 7 8 9 10 @Test void contextLoads () { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.isNotNull("name" ) .isNotNull("email" ) .ge("age" ,12 ); userMapper.selectList(wrapper).forEach(System.out::println); }
查询name是默烦的一个 用户
1 2 3 4 5 6 7 8 9 @Test void test2 () { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.eq("name" ,"默烦" ); User user = userMapper.selectOne(wrapper); System.out.println(user); }
查询age范围是 20 ~ 30 的用户
1 2 3 4 5 6 7 8 @Test void test3 () { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.between("age" ,20 ,30 ); Integer count = userMapper.selectCount(wrapper); System.out.println("用户数量为" +count); }
模糊查询
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); }
嵌套查询
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); }
通过id进行降序排序
1 2 3 4 5 6 7 8 @Test void test6 () { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.orderByDesc("id" ); List<User> users = userMapper.selectList(wrapper); users.forEach(System.out::println); }
5.2 LambdaQueryWrapper
在使用 QueryWrapper
时查询字段都是魔法值,都是由开发人员编写的列名。
如果需要重构代码,使用这种方式将十分麻烦,因此在 MP 中有 LambdaQueryWrapper
来解决这个问题。
比如,现将下述代码进行改写:
1 2 3 4 5 6 7 8 9 10 @Test void contextLoads () { QueryWrapper<User> wrapper = new QueryWrapper <>(); wrapper.isNotNull("name" ) .isNotNull("email" ) .ge("age" ,12 ); userMapper.selectList(wrapper).forEach(System.out::println); }
改写后的代码:
1 2 3 4 5 6 7 @Test public void testLambdaQueryWrapper () { LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().isNotNull(User::getName) .isNotNull(User::getEmail) .ge(User::getAge, 12 ); userMapper.selectList(wrapper).forEach(System.out::println); }
对于 QueryWrapper
对象的获取采用 new
关键字,而 LambdaQueryWrapper
对象的获取则习惯采用 Wrappers
的静态方法获取。
注意: 在使用 MP 的条件构造器时,请注意 and()
方法的使用规则,并区分与 or()
方法的区别。
需要注意的是,在低版本的 MP 中,可能并没有 Wrappers
类(请以参考文档为准)。
6. 代码生成器
官方参考文档:代码生成器
首先导入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > Latest Version</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-generator</artifactId > <version > 3.4.1</version > </dependency > <dependency > <groupId > org.apache.velocity</groupId > <artifactId > velocity-engine-core</artifactId > <version > 2.0</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 package com.yang;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 javax.xml.stream.FactoryConfigurationError;import java.util.ArrayList;public class MofanCode { public static void main (String[] args) { AutoGenerator mpg = new AutoGenerator (); GlobalConfig gc = new GlobalConfig (); String projectPath = System.getProperty("user.dor" ); gc.setOutputDir(projectPath+"/src/main/java" ); gc.setAuthor("默烦" ); gc.setOpen(false ); gc.setFileOverride(false ); gc.setServiceName("%sService" ); gc.setIdType(IdType.ID_WORKER); gc.setDateType(DateType.ONLY_DATE); gc.setSwagger2(true ); mpg.setGlobalConfig(gc); DataSourceConfig dsc = new DataSourceConfig (); dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=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("blog" ); pc.setParent("com.yang" ); pc.setEntity("pojo" ); pc.setMapper("mapper" ); pc.setService("service" ); pc.setController("controller" ); mpg.setPackageInfo(pc); StrategyConfig strategy = new StrategyConfig (); strategy.setInclude("user" ,"tags" ); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true ); strategy.setLogicDeleteFieldName("deleted" ); TableFill create_time = new TableFill ("create_time" , FieldFill.INSERT); TableFill update_time = new TableFill ("update_time" , FieldFill.INSERT_UPDATE); ArrayList<TableFill> tableFills = new ArrayList <>(); tableFills.add(create_time); tableFills.add(update_time); strategy.setTableFillList(tableFills); strategy.setVersionFieldName("version" ); strategy.setRestControllerStyle(true ); strategy.setControllerMappingHyphenStyle(true ); mpg.setStrategy(strategy); mpg.execute(); } }
以上代码按要求修改即可使用。
7. 拓展批量处理
7.1 批量插入
注意: 拓展的批量插入只适用于 MySQL 数据库。
在 MyBatis-Plus 中存在现成的批量插入方法,我们只需进行简单的拓展即可使用:
1、自定义 Injector 并继承 DefaultSqlInjector
类;
2、重写 getMethodList()
方法,添加现成的批量插入类,并注入 MyBatis-Plus配置类中;
3、最后拓展通用 Mapper 即可。
自定义 Injector 并重写 getMethodList()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class EasySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList (Class<?> mapperClass) { List<AbstractMethod> methodList = super .getMethodList(mapperClass); methodList.add(new InsertBatchSomeColumn ()); return methodList; } }
在 MyBatis-Plus 配置类中注入自定义 Injector:
1 2 3 4 @Bean public EasySqlInjector easySqlInjector () { return new EasySqlInjector (); }
拓展通用 Mapper,后续的 Mapper 接口不再继承 BaseMapper
,而是继承拓展的通用 Mapper。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface MybatisBaseMapper <T> extends BaseMapper <T> { int insertBatchSomeColumn (List<T> entityList) ; }
使用方法举例:
1 2 3 @Mapper @Repository public interface UserInfoDao extends MybatisBaseMapper <UserInfo> {}
7.2 批量更新
相较于简单的批量插入拓展,在拓展批量更新时需要我们自己编写批量更新模板方法,然后其他步骤就和批量插入的拓展类似。
首先在数据库连接上添加 allowMultiQueries=true
属性:
1 2 3 4 5 6 datasource: username: xxx password: xxx url: jdbc:mysql:///xxx?serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&allowMultiQueries=true driver-class-name: com.mysql.cj.jdbc.Driver
批量更新模板方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Slf4j public class UpdateBatchMethod extends AbstractMethod { private static final long serialVersionUID = 1147163282820238330L ; @Override public MappedStatement injectMappedStatement (Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { String sql = "<script>\n<foreach collection=\"list\" item=\"item\" separator=\";\">\nupdate %s %s where %s=#{%s} %s\n</foreach>\n</script>" ; String additional = tableInfo.isWithVersion() ? tableInfo.getVersionFieldInfo().getVersionOli("item" , "item." ) : "" + tableInfo.getLogicDeleteSql(true , true ); String setSql = sqlSet(tableInfo.isWithLogicDelete(), false , tableInfo, false , "item" , "item." ); String sqlResult = String.format(sql, tableInfo.getTableName(), setSql, tableInfo.getKeyColumn(), "item." + tableInfo.getKeyProperty(), additional); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sqlResult, modelClass); return this .addUpdateMappedStatement(mapperClass, modelClass, "updateBatch" , sqlSource); } }
在自定义的 Injector 添加拓展的批量更新模板类:
1 2 3 4 5 6 7 8 9 10 11 public class EasySqlInjector extends DefaultSqlInjector { @Override public List<AbstractMethod> getMethodList (Class<?> mapperClass) { List<AbstractMethod> methodList = super .getMethodList(mapperClass); methodList.add(new InsertBatchSomeColumn ()); methodList.add(new UpdateBatchMethod ()); return methodList; } }
最后拓展通用 Mapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface MybatisBaseMapper <T> extends BaseMapper <T> { int insertBatchSomeColumn (List<T> entityList) ; int updateBatch (@Param("list") List<T> entityList) ; }
批量更新使用注意事项
1、仅测试过 MySQL 数据库,对于其他数据库的支持尚不明确;
2、无法做到批量更新字段值为 NULL
,针对这种情况请自行编写 XML,比如:
1 2 3 4 5 6 7 8 9 10 11 <update id ="resetExamSubject" > <foreach collection ="list" item ="item" index ="index" close ="" open ="" separator =";" > UPDATE tb_exam_subject <set > examination_location = NULL, used = false </set > WHERE id = ${item.id} AND is_deleted = 0 </foreach > </update >
8. 通用枚举
在 JDK 1.5 之后引入了枚举,如果要在 MP 中实现通用枚举也很简单,只需要配置枚举类的路径,然后使用注解即可。
为了规范枚举的使用,习惯让自定义枚举继承一个接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface EnumInfo extends Serializable { String code () ; String message () ; }
假设有这样一个枚举:
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 public enum MajorTypeEnum implements EnumInfo { ACADEMIC("Academic" , "学术型" ), PROFESSIONAL_DEGREE("ProfessionalDegree" , "专业学位" ); private String code; private String message; MajorTypeEnum(String code, String message) { this .code = code; this .message = message; } @Override public String code () { return this .code; } @Override public String message () { return this .message; } }
这个枚举类所在的包路径为:indi.mofan.common.enums
。
现在我想让这个枚举保存到数据库中的值是 code,序列化后得到的是 message。那么首先需要在配置文件中指定枚举所在的包路径:
3.5.2 版本开始,无需设置扫描的枚举包!
1 2 3 mybatis-plus: type-enums-package: indi.mofan.common.enums
然后在 code 属性上使用 @EnumValue
注解:
1 2 @EnumValue private String code;
在 message 上使用 @JsonValue
注解即可:
1 2 @JsonValue private String message;
9. 常用配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 mybatis-plus: mapper-locations: classpath:indi/mofan/mybatis/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 table-prefix: tb_ id-type: assign_id type-enums-package: indi.mofan.common.enums
注意: 在 SpringBoot 项目中,我们常把 MyBatis 的 SQL 映射文件放在 resources 目录下。