MybatisPlus
1. 什么是mybatis-plus
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
极大的简化了开发~,基本的sql可以不用写。
2. 快速入门体验
- 导入依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.3</version> </dependency>
- 创建数据库和对应的表
通过mysql客户端或者命令行创建数据库,我的名字叫做
mybatis-lus
,通过idea连接mysql,并创建表创建表的语句如下:
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');
- 实体类和代码编写
-
创建个
pojo
的包,并在包下创建一个实体类,实体类的代码如下:@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
- 创建个mapper包,并在mapper下创建个UserMapper类,代码如下:
@Repository public interface UserMapper extends BaseMapper<User> { }
- 主程序加
@MapperScan
注解// 扫描mapper文件夹 @MapperScan("com.liam.mapper") @SpringBootApplication public class SpringbootMybatisPlusApplication { public static void main(String[] args) { SpringApplication.run(SpringbootMybatisPlusApplication.class, args); } }
- 测试
@SpringBootTest class SpringbootMybatisPlusApplicationTests { @Autowired private UserMapper userMapper; @Test public void testSelect() { System.out.println(("----- selectAll method test ------")); // 代表查询数据库所有的数据 List<User> userList = userMapper.selectList(null); userList.forEach(System.out::println); } }
数据库中的数据为:
查出的结果为:
- 配置日志输出
- 配置日志输出
3. CRUD扩展
1. 插入
-
测试代码编写
@Test public void testInsert() { User user = new User(); user.setName("liam"); user.setAge(18); user.setEmail("12345@qq.com"); int insert = userMapper.insert(user); // 插入 System.out.println(insert); // 受影响行数 System.out.println(user); // 输出 }
- 结果分析
可以看到蓝色的部分
id
是自动生成的,在测试用例上面都没有设置setId
,其实是用到了一个默认的主键生成策略。 -
主键生成策略介绍(引用自官网)
值 | 描述 |
---|---|
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 长整型类型(please use ASSIGN_ID ) |
|
32位UUID字符串(please use ASSIGN_UUID ) |
|
分布式全局唯一ID 字符串类型(please use ASSIGN_ID ) |
最下面三种在3.4.3.3
版本的mybatis-plus
已弃用,默认使用的是ASSIGN_ID
这种方式,下面看下如何去配置不同的数据库自增策略。
- 代码
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.ASSIGN_ID) // 默认的 private Long id; private String name; private Integer age; private String email; }
可以通过以下源码看出,默认是
NONE
,主键为NULL
,同时主键是number
类型的,因此默认用ASSIGN_ID
策略。/** * 数据库ID自增 * <p>该类型请确保数据库设置了 ID自增 否则无效</p> */ AUTO(0), /** * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) */ NONE(1), /** * 用户输入ID * <p>该类型可以通过自己注册自动填充插件进行填充</p> */ INPUT(2), /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */ /** * 分配ID (主键类型为number或string), * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法) * * @since 3.3.0 */ ASSIGN_ID(3), /** * 分配UUID (主键类型为 string) * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-","")) */ ASSIGN_UUID(4);
ASSIGN_ID
测试结果可以看到生成的结果id和没有加
ASSIGN_ID
形式是一样的-
其他策略测试
- 改成自增的
AUTO
@TableId(type = IdType.AUTO)
数据库的主键id一定要设置为
AUTO_INCREATEMENT
,不然会报错- 改成
INPUT
自己输入
@TableId(type = IdType.INPUT)
将数据库id自增取消掉再测试一遍
可以看到出错了,这是因为传入的
id
是null。下面是正确的测试用例~
@Test public void testInsert() { User user = new User(); user.setId(6L); // 手动设置id user.setName("liam"); user.setAge(19); user.setEmail("12345@qq.com"); int insert = userMapper.insert(user); System.out.println(insert); // 受影响行数 System.out.println(user); // 输出 }
插入成功。
- 改成自增的
2. 更新测试
-
测试代码编写
@Test public void testUpdate() { User user = new User(); user.setId(6L); user.setName("liam"); user.setAge(20); int i = userMapper.updateById(user); System.out.println(i); }
- 运行结果分析
-
自动填充处理
-
数据库级别
- 添加数据库字段,创建时间和更新时间
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', `create_update` datetime DEFAULT CURRENT_TIMESTAMP, //注意下面两个字段 `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1438128594922323972 DEFAULT CHARSET=utf8mb4;
- 实体类添加属性
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.INPUT) private Long id; private String name; private Integer age; private String email; private Date createTime; private Date updateTime; }
- 测试效果
- 测试代码
@Test public void testUpdate() { User user = new User(); user.setId(6L); user.setName("liam"); user.setAge(21); // 改了这里 int i = userMapper.updateById(user); System.out.println(i); }
- 初始值
可以看到结果更新成功,并且更新时间也变了
-
代码级别
- 数据库更新时间和创建时间默认值去掉
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年龄', `email` varchar(50) DEFAULT NULL COMMENT '邮箱', `create_time` datetime DEFAULT NULL, //去掉了更新CURRENT_TIMESTAMP `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1438128594922323972 DEFAULT CHARSET=utf8mb4;
- 实体类添加
@TableField
注解@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.INPUT) private Long id; private String name; private Integer age; private String email; @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; }
- 添加Handler
public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }
当然也可以用官网上的其他方式,具体参考:自动填充功能
-
测试效果
测试代码(插入)
// 测试插入 @Test public void testInsert() { User user = new User(); user.setId(8L); user.setName("liam"); user.setAge(30); user.setEmail("12345@qq.com"); int insert = userMapper.insert(user); System.out.println(insert); // 受影响行数 System.out.println(user); // 输出 }
效果,运行测试用例前
运行测试用例后:
测试代码(更新)
// 把上面插入年龄改为25 @Test public void testUpdate() { User user = new User(); user.setId(8L); user.setName("liam"); user.setAge(25); int i = userMapper.updateById(user); System.out.println(i); }
效果:
注意两个问题
- 实体类中的时间用
LocalDateTime
类型 -
handler
中代码在插入时,需要设置如下:@Override public void insertFill(MetaObject metaObject) { log.info("start insert fill ...."); this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); this.strictInsertFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 注意这里,官网只给了一个,如果不写这个,会造成updateTime的值为null } @Override public void updateFill(MetaObject metaObject) { log.info("start update fill ...."); this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); }
-
3. 乐观锁
使用乐观锁的目的是:当要更新一条记录的时候,希望这条记录没有被别人更新
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
其中第三步的version
和oldVersion
如果相同,说明其他人活着其他线程没有改,如果是不相同,则其他线程更改了,和CAS
一样~
快速开始
- 数据库增加字段
version
,实体类增加字段和@version
注解 -
配置插件
@MapperScan("com.liam.mapper") @Configuration public class MybatisPlusConfig { /** * 新版 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; } }
- 测试效果
-
测试代码(单个线程)
// 把id为8的名字改为bob,同时version字段会变为2 @Test public void testOptimisticLocker(){ User user = userMapper.selectById(8L); user.setName("bob"); int i = userMapper.updateById(user); System.out.println(i); }
可以看到在更新时,条件不仅根据了
id
还有version
,version
的值变为了2 -
测试代码(模拟多线程)
@Test public void testOptimisticLocker2(){ // 这里查出来的version为2 User user = userMapper.selectById(8L); user.setName("bob"); // 这段会先处理,然后version值会变为3 User user1 = userMapper.selectById(8L); user.setName("lisa"); userMapper.updateById(user1); int i = userMapper.updateById(user); System.out.println(i); }
结果:
结果分析:
```sql
查出来version为2
==> Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user WHERE id=?
==> Parameters: 8(Long)
<== Columns: id, name, age, email, version, create_time, update_time
<== Row: 8, bob, 25, 12345@qq.com, 2, 2021-09-16 20:41:31, 2021-09-16 20:47:43
<== Total: 1
<hr />
// 这里查出来也为2
<span class="text-highlighted-inline" style="background-color: #fffd38;">> Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user WHERE id=?
</span>> Parameters: 8(Long)
<<span class="text-highlighted-inline" style="background-color: #fffd38;"> Columns: id, name, age, email, version, create_time, update_time
<</span> Row: 8, bob, 25, 12345@qq.com, 2, 2021-09-16 20:41:31, 2021-09-16 20:47:43
<<span class="text-highlighted-inline" style="background-color: #fffd38;"> Total: 1</span>
version的值为2,和数据库中的值一致,更新,得到3
<span class="text-highlighted-inline" style="background-color: #fffd38;">> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?
</span>> Parameters: bob(String), 25(Integer), 12345@qq.com(String), 3(Integer), 2021-09-16T20:41:31(LocalDateTime), 2021-09-16T20:47:43(LocalDateTime), 8(Long), 2(Integer)
<h2><<span class="text-highlighted-inline" style="background-color: #fffd38;"> Updates: 1</span></h2>
数据库中的version值为3了,而之前查出来是2,不一致,更新失败
<span class="text-highlighted-inline" style="background-color: #fffd38;">> Preparing: UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?
</span>> Parameters: lisa(String), 25(Integer), 12345@qq.com(String), 3(Integer), 2021-09-16T20:41:31(LocalDateTime), 2021-09-16T20:47:43(LocalDateTime), 8(Long), 2(Integer)
<<span class="text-highlighted-inline" style="background-color: #fffd38;"> Updates: 0
```
4. 查询
- 根据id查询
- 代码
@Test public void testSelectById() { User user = userMapper.selectById(1L); System.out.println(user); }
- 结果
-
根据id列表查询
- 测试代码
@Test public void testSelectByIds() { List<User> user = userMapper.selectBatchIds(Arrays.asList(1L,2L,3L)); user.forEach(System.out::println); }
- 结果
-
根据条件查询
- 测试代码
@Test public void testSelectByCondition() { HashMap<String, Object> map = new HashMap<>(); map.put("name", "liam"); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); }
- 结果
-
分页查询
- 导入插件
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 分页插件 mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); return mybatisPlusInterceptor; }
- 代码测试
@Test public void testPage() { Page<User> page = new Page<>(1, 5); Page<User> page1 = userMapper.selectPage(page, null); List<User> records = page1.getRecords(); records.forEach(System.out::println); }
- 结果
4. 删除
-
物理删除
- 代码
@Test public void testDeleteById() { int i = userMapper.deleteById(1438128594922323971L); System.out.println(i); }
- 结果
-
代码
@Test public void testDeleteByIds() { int i = userMapper.deleteBatchIds(Arrays.asList(5L, 6L, 1438128594922323970L)); System.out.println(i); }
- 结果
-
逻辑删除
- 配置
mybatis-plus: global-config: db-config: logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
- 表中加字段
默认值设置为0
- 实体类中加字段
@Data @AllArgsConstructor @NoArgsConstructor public class User { @TableId(type = IdType.INPUT) private Long id; private String name; private Integer age; private String email; @Version private int version; // 增加deleted字段 @TableLogic private Integer deleted; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
- 测试效果
- 结果分析
实际上删除只是做了更新操作
- 测试查询操作
@Test public void testSelectById() { User user = userMapper.selectById(1L); System.out.println(user); }
- 结果效果
- 结果分析
在查询的时候其实是带上了
deleted
= 0 也就是查询没被删除的数据,那如果想要查询已经删除了的,如何去做呢?需要通过自定义sql。- 如何自定义sql
自定义sql,上面看到查询的话,逻辑 删除了的是查不出来的,如果要查的话,只能自定义了
- 测试代码
@Repository public interface UserMapper extends BaseMapper<User> { //{ew.customSqlSegment} 为占位符,代表查询条件 @Select("select * from user{ew.customSqlSegment}") List<User> selectAll(@Param(Constants.WRAPPER) Wrapper<User> wrapper); }
@Test public void selectDIY() { QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.eq("name", "bob"); wrapper.eq("deleted", 1); List<User> users = userMapper.selectAll(wrapper); users.forEach(System.out::println); }
- 结果
可以看到查出来了
5. 条件构造器
-
相等
- 代码
@Test public void testSelectWrapperEQ() { QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.eq("age", 18); userMapper.selectList(wrapper).forEach(System.out::println); }
- 结果
-
大于
- 代码
@Test public void testSelectWrapperGt() { QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.ge("age", 18); userMapper.selectList(wrapper).forEach(System.out::println); }
- 结果
-
两数之间
- 代码
@Test public void testSelectWrapperBt() { QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.between("age", 18, 21); userMapper.selectList(wrapper).forEach(System.out::println); }
- 结果
-
模糊查询
- 代码
@Test public void testSelectWrapperLk() { QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.like("name", "J"); userMapper.selectList(wrapper).forEach(System.out::println); }
- 结果
-
排序
- 代码
@Test public void testSelectWrapperOrder() { QueryWrapper<User> wrapper = new QueryWrapper<User>(); wrapper.orderByAsc("id").last("limit 1"); userMapper.selectList(wrapper).forEach(System.out::println); }
- 结果
6. 自动生成代码
package com.liam.generator;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @Author fang ping
* @Date 2021/9/18 12:05 上午
* @Version 1.0
*/
public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("liam");
gc.setOpen(false);
gc.setServiceName("%sService");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis-lus?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.liam");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
// strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
文章评论