Mybatis-Plus
# MyBatisPlus概述
简介
需要的基础:把我的MyBatis、Spring、SpringMVC就可以学习这个了!
为什么要学习它呢?MyBatisPlus可以节省我们大量工作时间,所有的CRUD代码它都可以自动化完成!
JPA 、 tk-mapper、MyBatisPlus
偷懒的!
是什么? MyBatis 本来就是简化 JDBC 操作的!
官网 (opens new window):MyBatis Plus,简化 MyBatis !
特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作, BaseMapper
强大的 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
所有学不会都是给懒找的借口!伸手党,白嫖党!
# 快速入门
地址:[https://mp.baomidou.com/guide/quick-start.html#]{.ul} (opens new window)初始化工程 使用第三方组件:
1、导入对应的依赖
2、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力!
步骤
1、创建数据库
2、创建user表
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 )
);
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' );
-- 真实开发中,version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
3、编写项目,初始化项目!使用SpringBoot初始化!
4、导入依赖
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus -->
<!-- mybatis-plus 是自己开发,并非官方的! -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
说明:我们使用 mybatis-plus 可以节省我们大量的代码,尽量不要同时导入 mybatis
和 mybatis- plus
!版本的差异!
5、连接数据库!这一步和 mybatis 相同!
# mysql 8 驱动不同com.mysql.cj.jdbc.Driver、需要增加时区的配置
serverTimezone=GMT%2B8
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
2
3
4
5
6
6、编写pojo、service、controller
==以前的做法==:
- 传统方式pojo-dao(连接mybatis,配置mapper.xml文件)-service-controller
==使用了mybatis-plus 之后:==
- pojo
package com.sauljwu.mybatisdemo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- mapper接口
package com.sauljwu.mybatisdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sauljwu.mybatisdemo.pojo.User;
import org.springframework.stereotype.Repository;
/**
* @author SaulJ
*
*/
@Repository // 代表持久层
public interface UserMapper extends BaseMapper<User> {
// 所有的CRUD操作都已经编写完成了
// 你不需要像以前的配置一大堆文件了
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意点,我们需要在主启动类上去扫描我们的mapper包下的所有接口
package com.sauljwu.mybatisdemo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.sauljwu.mybatisdemo.mapper")
@SpringBootApplication
public class MybatisdemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisdemoApplication.class, args);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试类中测试
@Test
void testList(){
userMapper.selectList(null).forEach(System.out::println);
}
2
3
4
结果
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)
2
3
4
5
思考
1、SQL谁帮我们写的 ? MyBatis-Plus 都写好了
2、方法哪里来的? MyBatis-Plus 都写好了
# 配置日志
我们所有的sql现在是不可见的,我们希望知道它是怎么执行的,所以我们必须要看日志!
# 日期配置
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2
重新运行刚才的查询案例,输出结果:
配置完毕日志之后,后面的学习就需要注意这个自动生成的SQL,你们就会喜欢上 MyBatis-Plus!
# 插入操作
insert插入
@Test
void testInsert() {
User user = new User();
user.setName("张三");
user.setAge(18);
int result = userMapper.insert(user);
System.out.println("result = " + result);
System.out.println("user = " + user);
}
2
3
4
5
6
7
8
9
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6482eef] was not registered for synchronization because synchronization is not active
2020-12-10 12:12:31.525 INFO 22548 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2020-12-10 12:12:32.504 INFO 22548 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@231182885 wrapping com.mysql.cj.jdbc.ConnectionImpl@30e9ca13] will not be managed by Spring
==> Preparing: INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
==> Parameters: 1337067681822633985(Long), 张三(String), 18(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6482eef]
result = 1
user = User(id=1337067681822633985, name=张三, age=18, email=null)
2
3
4
5
6
7
8
9
10
11
可以看到:
- 自带主键id:全局的唯一id
- insert插入会返回影响的行数
- 插入后的数据自动返回
# 主键生成策略
默认 ID_WORKER 全局唯一id
分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法:
snowflake
是Twitter
开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
主键自增
mybatis-plus也提供了主键自增的策略,需要手动配置:
1、pojo实体字段加上@TableId(type = IdType.AUTO)
2、数据库字段一定要是自增!
修改完毕后,再次测试插入数据。
可以看到,最新记录的主键跟上一条记录的主键,它们是自增策略。
其他主键策略
publicenumIdType {
AUTO(0), // 数据库id自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ID_WORKER(3), // 默认的全局唯一id
UUID(4), // 全局唯一id uuid
ID_WORKER_STR(5); //ID_WORKER 字符串表示法
}
2
3
4
5
6
7
8
其他CRUD操作我就不一一填写了,具体可以参考官网 (opens new window)。
# 自动填充
创建时间、修改时间!这些个操作一遍都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create
、gmt_modified
几乎所有的表都要配置上!而且需要自动化!
方式一:数据库级别
注意:工作中不允许你修改数据库。
实现步骤:
1、在表中新增字段
2、同步实体
3、编写拦截处理器
具体实现:
1、在表中新增字段:create_time
、update_time
2、同步实体类
// 字段添加填充内容
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
2
3
4
5
6
3、拦截处理器
package com.sauljwu.mybatisdemo.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author SaulJ
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
}
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
==官网给的是LocalDateTime.class
和LocalDateTime.now()
并不适用于字段为Date
类型的实体类==
也可能是我哪里操作不对吧,如果有人知道就告诉我。
完成三个步骤后,开始测试插入数据和更新数据,就发现已经能自动填充了。
# 乐观锁
在面试过程中,我们经常会被问道乐观锁,悲观锁!这个其实非常简单!
乐观锁:顾名思义,十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题, 再次更新值测试
悲观锁:故名思意十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
乐观锁实现方式:
- 取出记录,获取当前
version
- 更新时,带上这个
version
- 执行更新时,带上这个
version
作为条件 - 如果这时
version
和数据库中的version
不一致,就更新失败
例如:
-- A 乐观锁:1、先查询,获得版本号 version = 1
update user set name = "kuangshen", version = version + 1 where id = 2 and version = 1
-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "kuangshen", version = version + 1 where id = 2 and version = 1
2
3
4
5
6
配置Mybatis-Plus的乐观锁插件
1、给数据库中增加version
字段,设置默认值为1。
2、给实体类同样加上version
字段
@Version
private Integer version;
2
新版本不用自己写注册组件了。
3、注册组件
自己编写一个MybatisPlusConfig
,并且启动类的包扫描放在到这个配置类。
==旧版本==
@MapperScan("com.sauljwu.mybatisdemo.mapper")
@Configuration // 配置类
public class MyBatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
2
3
4
5
6
7
8
9
10
经过我一翻研究,Mybatis-Plus
==3.4.0以上==是如下这样配置:
package com.sauljwu.mybatisdemo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@MapperScan("com.sauljwu.mybatisdemo.mapper")
@Configuration
public class MybatisPlusConfig {
/**
* 新版本乐观锁配置
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
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 testOptimisticLockerInnerInterceptor(){
User user = userMapper.selectById(3L);
user.setAge(99);
userMapper.updateById(user);
}
2
3
4
5
6
7
可以看到,在数据库,version
版本已经更新为2
测试并发更新:
//测试并发更新
@Test
public void testUpdate2(){
User user1 = userMapper.selectById(1L);
user1.setAge(18);
User user2 = userMapper.selectById(1L);
user2.setAge(3);
userMapper.updateById(user2);
userMapper.updateById(user1);
}
2
3
4
5
6
7
8
9
10
可以看到,并发更新的时,如果version
和数据库中不一致,后更新的就失败了,这就是乐观锁。
记住步骤,一定要想取出记录,带上version
,然后再去更新,不然的话直接更新,是不会触发乐观锁的。
错误示范:
@Test
public void testUpdate() {
User user = new User();
user.setId(3L);
user.setName("test223");
userMapper.updateById(user);
}
2
3
4
5
6
7
# 代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
步骤:
1、添加依赖
2、编写生成器
添加依赖
MyBatis-Plus 从 3.0.3
之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:
- 添加 代码生成器 依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
2
3
4
5
- 添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎。
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
2
3
4
5
编写生成器
package com.sauljwu.mybatisdemo;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
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 java.util.ArrayList;
import java.util.Scanner;
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
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("jobob");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/ant?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("密码");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.baomidou.ant");
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(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();
}
}
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