概述
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具。
https://baomidou.com/
https://baomidou.com/pages/24112f/
使用
准备sql
1 2 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');
|
项目搭建
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
| <?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
1 2 3 4 5 6 7 8 9
| spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.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:
1 2 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); }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @RestController public 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
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
| { "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比较好。
如果为了快,其实也无所谓了。
分页搜索
配置分页插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Configuration public class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
|
无条件的分页搜索
1 2 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/
1 2 3 4 5
| IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
MyPage selectPageVo(MyPage page);
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
|
1 2 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,即给于表一个别名,字段也要 别名.字段。
实践
1 2 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);
|
1 2 3 4 5 6 7
| @Override public 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); }
|
1 2 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注解:
1 2
| @TableId(type = IdType.ASSIGN_ID) private Long id;
|
1 2 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/
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 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; }
}
|
1 2 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字段
1 2
| @TableId(type = IdType.ASSIGN_ID) private Long id;
|
测试controller不变。
发现数据成功保存,并打印生成ID的日志。
逻辑删除插件
添加数据库表的字段:
1 2
| ALTER TABLE `user` ADD COLUMN `deleted` int(0) DEFAULT(0) COMMENT '逻辑删除字段' AFTER `email`;
|
重新生成mapper、xml。
全局配置
1 2 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
|
每个表字段不一致
1 2 3 4 5
|
@TableLogic private Integer deleted;
|
测试
1 2 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可以逻辑删除。
乐观锁插件
1 2
| ALTER TABLE `user` ADD COLUMN `version` int(0) NULL DEFAULT 0 COMMENT '版本号' AFTER `deleted`;
|
相应的sql与实体要改或者生成。
1 2
| @Version private Integer version;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Configuration public class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
|
1 2 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版本的)
1 2 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>
|
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
| 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方法。