概述
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具。
https://baomidou.com/
https://baomidou.com/pages/24112f/
使用
准备sql
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | CREATE TABLE `user`(
 id BIGINT NOT NULL COMMENT '主键ID',
 name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
 age INT 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');
 
 | 
项目搭建
| 12
 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
 
 | <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.7.17</version>
 <relativePath/>
 </parent>
 <groupId>com.example</groupId>
 <artifactId>spring-boot-mybatisplus-test</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>spring-boot-mybatisplus-test</name>
 <description>spring-boot-mybatisplus-test</description>
 <properties>
 <java.version>17</java.version>
 </properties>
 <dependencies>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
 </dependency>
 
 <dependency>
 <groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-boot-starter</artifactId>
 <version>3.5.4</version>
 </dependency>
 
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 </dependency>
 
 <dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.32</version>
 </dependency>
 
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 </dependency>
 </dependencies>
 
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
 
 </project>
 
 | 
application.properties
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | spring.datasource.type=com.zaxxer.hikari.HikariDataSourcespring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=true\
 &useUnicode=true\
 &characterEncoding=UTF-8\
 &useServerPrepStmts=true\
 &serverTimezone=Asia/Shanghai\
 &zeroDateTimeBehavior=CONVERT_TO_NULL
 spring.datasource.username=root
 spring.datasource.password=123456
 
 | 
在idea中添加mybatisX插件:
https://baomidou.com/pages/ba5b24/
添加数据源-mysql- 选择数据库-在user表上右键点击 myabtisx-generator
选择模块
输入base package
点击下一步
annotaion使用 mybatisplus3
options 加上lombok
template使用mybatisplus3
点击finsh生成代码。
这样mapper 、 xml 、 service层的代码都有了。
添加@MapperScan注解扫描mapper:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | @SpringBootApplication@MapperScan("com.example.springbootmybatisplustest.mapper")
 public class SpringBootMybatisplusTestApplication {
 
 public static void main(String[] args) {
 SpringApplication.run(SpringBootMybatisplusTestApplication.class, args);
 }
 
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | @RestControllerpublic class UserController {
 
 @Autowired
 private UserService userService;
 
 @GetMapping("/list")
 public Map<String, Object> list() {
 Map<String, Object> result = new HashMap<>();
 result.put("code", 200);
 result.put("msg", "成功");
 result.put("data", userService.list());
 return result;
 }
 
 }
 
 | 
测试
http://localhost:8080/list
| 12
 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
 
 | {"msg": "成功",
 "code": 200,
 "data": [{
 "id": 1,
 "name": "Jone",
 "age": 18,
 "email": "test1@baomidou.com"
 }, {
 "id": 2,
 "name": "Jack",
 "age": 20,
 "email": "test2@baomidou.com"
 }, {
 "id": 3,
 "name": "Tom",
 "age": 28,
 "email": "test3@baomidou.com"
 }, {
 "id": 4,
 "name": "Sandy",
 "age": 21,
 "email": "test4@baomidou.com"
 }, {
 "id": 5,
 "name": "Billie",
 "age": 24,
 "email": "test5@baomidou.com"
 }]
 }
 
 | 
有条件的搜索
官网上说用条件构造器,能用还是能用的,但是还觉得代码与数据库字段没有隔离开,所以遇到有条件的搜索还是自定义sql比较好。
如果为了快,其实也无所谓了。
分页搜索
配置分页插件
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | @Configurationpublic class MybatisPlusConfig {
 
 
 
 
 @Bean
 public MybatisPlusInterceptor mybatisPlusInterceptor() {
 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
 
 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
 
 
 return interceptor;
 }
 }
 
 | 
无条件的分页搜索
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | @GetMapping("/page")public Map<String, Object> page() {
 Map<String, Object> result = new HashMap<>();
 result.put("code", 200);
 result.put("msg", "成功");
 Page<User> page = new Page<>();
 page.setSize(2);
 page.setCurrent(1);
 result.put("data", userService.page(page));
 return result;
 }
 
 | 
有条件的分页搜索
https://baomidou.com/pages/97710a/
| 12
 3
 4
 5
 
 | IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
 MyPage selectPageVo(MyPage page);
 
 List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
 
 | 
| 12
 3
 
 | <select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">SELECT id,name FROM user WHERE state=#{state}
 </select>
 
 | 
如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage; 如果想临时不分页,可以在初始化IPage时size参数传 <0 的值;
如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);
如果 xml 需要从 page 里取值,需要 page.属性 获取。
生成 countSql 会在 left join的表不参与 where条件的情况下,把 left join优化掉。
所以建议任何带有 left join 的sql,都写标准sql,即给于表一个别名,字段也要 别名.字段。
实践
| 12
 3
 4
 5
 
 | <select id="pageBySearch" resultMap="BaseResultMap">select <include refid="Base_Column_List"/>
 from user
 where name = #{name}
 </select>
 
 | 
| 1
 | IPage<User> pageBySearch(IPage<User> page, @Param("name") String name);
 | 
| 1
 | public IPage<User> pageBySearch(Integer pageNum, Integer pageSize, String name);
 | 
| 12
 3
 4
 5
 6
 7
 
 | @Overridepublic IPage<User> pageBySearch(Integer pageNum, Integer pageSize, String name) {
 Page<User> page = new Page<>();
 page.setCurrent(pageNum);
 page.setSize(pageSize);
 return getBaseMapper().pageBySearch(page, name);
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 
 | @GetMapping("/pageBySearch")public Map<String, Object> pageBySearch() {
 Map<String, Object> result = new HashMap<>();
 result.put("code", 200);
 result.put("msg", "成功");
 result.put("data", userService.pageBySearch(1, 1, "test"));
 return result;
 }
 
 | 
主键生成器
使用mybatisplus的生成器
在domain实体的@TableId注解:
| 12
 
 | @TableId(type = IdType.ASSIGN_ID)private Long id;
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | @GetMapping("/save")public Map<String, Object> save() {
 User user = new User();
 user.setName("test");
 user.setAge(20);
 user.setEmail("test@qq.com");
 userService.save(user);
 Map<String, Object> result = new HashMap<>();
 result.put("code", 200);
 result.put("msg", "成功");
 return result;
 }
 
 | 
http://localhost:8080/save
查看数据库中ID值。 更多的内容还是看官网吧
自定义ID生成器
https://baomidou.com/pages/568eb2/
| 12
 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
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 
 | 
 
 
 
 
 
 
 
 
 
 public class SnowFlake {
 
 
 
 
 private final static long START_STMP = 1480166465631L;
 
 
 
 
 private final static long SEQUENCE_BIT = 12;
 private final static long MACHINE_BIT = 5;
 private final static long DATACENTER_BIT = 5;
 
 
 
 
 
 
 
 private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
 
 private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
 
 
 
 
 
 
 
 
 
 
 
 
 
 private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
 
 
 
 
 
 
 
 private final static long MACHINE_LEFT = SEQUENCE_BIT;
 
 private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
 
 private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
 
 private long datacenterId;
 private long machineId;
 private long sequence = 0L;
 private long lastStmp = -1L;
 
 public static void main(String[] args) {
 SnowFlake snowFlake = new SnowFlake(2, 3);
 
 
 
 
 
 System.out.println(1 << 12);
 for (int i = 0; i < (1 << 12); i++) {
 System.out.println(snowFlake.nextId());
 }
 
 }
 
 
 
 
 
 public SnowFlake(long datacenterId, long machineId) {
 if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
 throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0(数据中心ID 不能大于 MAX_DATACENTER_NUM 或小于 0)");
 }
 if (machineId > MAX_MACHINE_NUM || machineId < 0) {
 throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0(机器ID 不能大于 MAX_MACHINE_NUM 或小于 0)");
 }
 this.datacenterId = datacenterId;
 this.machineId = machineId;
 }
 
 
 
 
 public synchronized long nextId() {
 
 long currStmp = getNewstmp();
 
 
 if (currStmp < lastStmp) {
 throw new RuntimeException("Clock moved backwards. Refusing to generate id(时钟回拨。拒绝生成 id)");
 }
 
 
 if (currStmp == lastStmp) {
 
 
 
 
 
 
 
 
 sequence = (sequence + 1) & MAX_SEQUENCE;
 
 if (sequence == 0L) {
 
 currStmp = getNextMill();
 }
 } else {
 
 sequence = 0L;
 }
 
 
 lastStmp = currStmp;
 
 return (currStmp - START_STMP) << TIMESTMP_LEFT
 | datacenterId << DATACENTER_LEFT
 | machineId << MACHINE_LEFT
 | sequence;
 }
 
 
 
 
 private long getNewstmp() {
 return System.currentTimeMillis();
 }
 
 
 
 
 
 private long getNextMill() {
 long mill = getNewstmp();
 while (mill <= lastStmp) {
 mill = getNewstmp();
 }
 return mill;
 }
 
 
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | @Component@Slf4j
 public class CustomIdGenerator implements IdentifierGenerator {
 @Override
 public Long nextId(Object entity) {
 SnowFlake snowFlake = new SnowFlake(2, 3);
 long id = snowFlake.nextId();
 log.info("生成的ID{}", id);
 
 return id;
 }
 }
 
 | 
同样User实体的ID字段
| 12
 
 | @TableId(type = IdType.ASSIGN_ID)private Long id;
 
 | 
测试controller不变。
发现数据成功保存,并打印生成ID的日志。
逻辑删除插件
添加数据库表的字段:
| 12
 
 | ALTER TABLE `user` ADD COLUMN `deleted` int(0) DEFAULT(0) COMMENT '逻辑删除字段' AFTER `email`;
 
 | 
重新生成mapper、xml。
全局配置
| 12
 3
 4
 5
 6
 
 | 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
 
 | 
每个表字段不一致
| 12
 3
 4
 5
 
 |    
 
 @TableLogic
 private Integer deleted;
 
 | 
测试
| 12
 3
 4
 5
 6
 7
 8
 
 | @GetMapping("/delete")public Map<String, Object> delete() {
 userService.removeById(1L);
 Map<String, Object> result = new HashMap<>();
 result.put("code", 200);
 result.put("msg", "成功");
 return result;
 }
 
 | 
发现如果字段一开始为null不能逻辑删除。默认值为0可以逻辑删除。
乐观锁插件
| 12
 
 | ALTER TABLE `user` ADD COLUMN `version` int(0) NULL DEFAULT 0 COMMENT '版本号' AFTER `deleted`;
 
 | 
相应的sql与实体要改或者生成。
| 12
 
 | @Versionprivate Integer version;
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | @Configurationpublic class MybatisPlusConfig {
 
 
 
 
 @Bean
 public MybatisPlusInterceptor mybatisPlusInterceptor() {
 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
 
 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
 
 
 return interceptor;
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | @GetMapping("/updateByVersion")public Map<String, Object> updateByVersion() {
 Map<String, Object> result = new HashMap<>();
 result.put("code", 200);
 result.put("msg", "成功");
 User user = new User();
 user.setId(1L);
 user.setName("test");
 user.setVersion(1);
 result.put("data", userService.updateById(user));
 return result;
 }
 
 | 
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
mybatisplus乐观锁插件不支持null。必须要转version字段。
说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下 newVersion = oldVersion + 1
- newVersion会回写到- entity中
- 仅支持 updateById(id)与update(entity, wrapper)方法
- 在 update(entity, wrapper)方法下,wrapper不能复用!!!
通用枚举
感觉上也不好用,因为一会儿返回是数值类型,一会儿返回是字符串类型,这时前端会不会懵了。回显再修改提交。
生成器
如果idea不支持database及mybatisx。(比如mac版本的)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <dependency><groupId>com.baomidou</groupId>
 <artifactId>mybatis-plus-generator</artifactId>
 <version>3.5.4</version>
 </dependency>
 
 <dependency>
 <groupId>org.freemarker</groupId>
 <artifactId>freemarker</artifactId>
 <version>2.3.31</version>
 </dependency>
 
 | 
| 12
 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
 
 | import com.baomidou.mybatisplus.generator.FastAutoGenerator;import com.baomidou.mybatisplus.generator.config.OutputFile;
 import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
 import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
 
 import java.sql.Types;
 import java.util.Collections;
 
 public class MybatisPlusGenerator {
 
 public static void main(String[] args) {
 FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/test", "root", "root123456")
 .globalConfig(builder -> {
 builder.author("hqc")
 
 .fileOverride()
 .outputDir("/Users/qichuanhan/Documents/ideaworkspace/rocketmq-test/src/main/java");
 })
 .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
 int typeCode = metaInfo.getJdbcType().TYPE_CODE;
 if (typeCode == Types.SMALLINT) {
 
 return DbColumnType.INTEGER;
 }
 return typeRegistry.getColumnType(metaInfo);
 
 }))
 .packageConfig(builder -> {
 builder.parent("com.example.rocketmqtest")
 
 .pathInfo(Collections.singletonMap(OutputFile.xml, "/Users/qichuanhan/Documents/ideaworkspace/rocketmq-test/src/main/resources/mapper"));
 })
 .strategyConfig(builder -> {
 builder.addInclude("user");
 
 })
 .templateEngine(new FreemarkerTemplateEngine())
 .execute();
 }
 
 }
 
 | 
运行main方法。