简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

官网:https://baomidou.com/

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

支持数据库

  • mysql 、 mariadb 、 oracle 、 db2 、 h2 、 hsql 、 sqlite 、 postgresql 、 sqlserver 、 presto
  • 达梦数据库 、 虚谷数据库 、 人大金仓数据库

框架结构

在这里插入图片描述

快速开始

创建数据库表

1
2
3
4
5
6
7
8
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)
);
1
2
3
4
5
6
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');

创建SpringBoot项目,添加依赖

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
<?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.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.yanghuisen</groupId>
<artifactId>mp-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mp-demo</name>
<description>Demo project for MyBatis-Plus</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--MyBatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

注:如果引入了MyBatis-Plus就不要在引入MyBtais,两者选一

配置数据库账号密码

修改application.yml文件

1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mpdemo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8

编码

创建实体类

使用Lombok减少代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.yanghuisen.mpdemo.entity;

import lombok.Data;

/**
* @author Y
* @date 2020/8/15 20:41
* @desc 用户实体类
*/
@Data
public class User {

private Long id;

private String name;

private Integer age;

private String email;
}

mapper接口

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.yanghuisen.mpdemo.mapper;

import cn.yanghuisen.mpdemo.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
* @author Y
* @date 2020/8/15 20:39
* @desc 用户Mapper接口
*/
public interface UserMapper extends BaseMapper<User> {
}

UserMapper接口继承BaseMapper,泛型参数为实体类对象

配置扫描注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.yanghuisen.mpdemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author Y
* @date 2020/8/15 20:38
* @desc 启动类
*/
@SpringBootApplication
@MapperScan("cn.yanghuisen.mpdemo.mapper")
public class MpDemoApplication {

public static void main(String[] args) {
SpringApplication.run(MpDemoApplication.class, args);
}

}

使用@MapperScan指定扫描的mapper接口路径

开始使用

查询所有的数据

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
package cn.yanghuisen.mpdemo;

import cn.yanghuisen.mpdemo.entity.User;
import cn.yanghuisen.mpdemo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@SpringBootTest // 测试注解
@RunWith(SpringRunner.class) // 使用Spring环境
@Slf4j // 日志打印
class MpDemoApplicationTests {

@Autowired
private UserMapper userMapper;

@Test
void selectListTest() {
log.info("*****查询User列表*****");
List<User> userList = userMapper.selectList(null);
log.info("*****打印userList*****");
userList.forEach(System.out::println);
}

}
1
2
3
4
5
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)

到此就可以体会到MyBatis-Plus的强大魅力,只需要简单的配置就可以实现基本的CRUD操作。

日志配置

开始日志,查看具体SQL命令

application.yml

1
2
3
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

image.png

注解

@TableName

表名注解

属性 类型 必须指定 默认值 描述
value String “” 表名
schema String “” schema
keepGlobalPrefix boolean false 是否保持使用全局的 tablePrefix 的值(如果设置了全局 tablePrefix 且自行设置了 value 的值)
resultMap String “” xml 中 resultMap 的 id
autoResultMap boolean false 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建并注入)

@TableId

主键注解

属性 类型 必须指定 默认值 描述
value String “” 主键字段名
type Enum IdType.NONE 主键类型

IdType

主键生成方式

描述
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_WORKER 分布式全局唯一ID 长整型类型(please use ASSIGN_ID)
UUID 32位UUID字符串(please use ASSIGN_UUID)
ID_WORKER_STR 分布式全局唯一ID 字符串类型(please use ASSIGN_ID)

@TableField

字段注解(非主键)

属性 类型 必须指定 默认值 描述
value String “” 数据库字段名
el String “” 映射为原生 #{ ... } 逻辑,相当于写在 xml 里的 #{ ... } 部分
exist boolean true 是否为数据库表字段
condition String “” 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考
update String “” 字段 update set 部分注入, 例如:update=”%s+1”:表示更新时会set version=version+1(该属性优先级高于 el 属性)
insertStrategy Enum N DEFAULT 举例:NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
updateStrategy Enum N DEFAULT 举例:IGNORED: update table_a set column=#{columnProperty}
whereStrategy Enum N DEFAULT 举例:NOT_EMPTY: where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
fill Enum FieldFill.DEFAULT 字段自动填充策略
select boolean true 是否进行 select 查询
keepGlobalFormat boolean false 是否保持使用全局的 format 进行处理
jdbcType JdbcType JdbcType.UNDEFINED JDBC类型 (该默认值不代表会按照该值生效)
typeHandler Class<? extends TypeHandler> UnknownTypeHandler.class 类型处理器 (该默认值不代表会按照该值生效)
numericScale String “” 指定小数点后保留的位数

FieldStrategy

描述
IGNORED 忽略判断
NOT_NULL 非NULL判断
NOT_EMPTY 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
DEFAULT 追随全局配置

FieldFill

字段填充策略

描述
DEFAULT 默认不处理
INSERT 插入时填充字段
UPDATE 更新时填充字段
INSERT_UPDATE 插入和更新时填充字段

@TableLogic

表字段逻辑处理注解(逻辑删除)

属性 类型 必须指定 默认值 描述
value String “” 逻辑未删除值
delval String “” 逻辑删除值

CRUD接口

MapperCRUD接口

这里只是一部分查询接口,完整的查询接口请查看官方文档。

insert-插入数据

1
2
3
4
5
6
7
8
9
10
@Test
void insertTest(){
User user = new User();
user.setName("Ahh");
user.setAge(20);
user.setEmail("admin@yanghuisen.cn");
userMapper.insert(user);
log.info(user.toString());
selectListTest();
}

调用insert方法进行数据插入,传入一个user对象。插入成功后会把生成的ID,回填到user对象中。

注:默认使用的是雪花算法生成对应的ID,如果要修改ID生成算法可以在实体类上添加注解@TableId

1
2
3
4
5
6
7
8
9
10
11
public class User {

@TableId(type = IdType.AUTO)
private Long id;

private String name;

private Integer age;

private String email;
}

注:如果要使用auto自增主键的话,需要开启数据库的主键ID自增。

update-更新数据

1
2
3
4
5
6
7
8
9
10
11
@Test
void updateTest(){
User user = new User();
user.setId(6L);
user.setName("啊哈哈");
user.setAge(20);
user.setEmail("admin@yanghuisen.cn");
int i = userMapper.updateById(user);
log.info(i+"");
selectListTest();
}

注:updateById传入的是user对象,不是具体的ID。

select-查询数据

批量查询

1
2
3
4
5
@Test
void selectUserByBatchId(){
List<User> userList = userMapper.selectBatchIds(Arrays.asList(1,2,3,4,5));
userList.forEach(System.out::println);
}

Map条件查询

1
2
3
4
5
6
7
8
@Test
void selectUserByMap(){
Map<String,Object> map = new HashMap<>();
map.put("id",1);
map.put("name","Jone");
List<User> userList = userMapper.selectByMap(map);
userList.forEach(System.out::println);
}

delete-删除数据

1
2
3
4
5
@Test
void deleteUserById(){
userMapper.deleteById(1302239794451763202L);
selectListTest();
}

删除同样有批量删除和map删除,使用方式和查询的一样

条件构造器

细心的就会发现,上面的CRUD都是一些简单的操作,如果是复杂的就没办法了,所以这时就要用到了条件构造器。

条件构造器有很多,具体的还请查看官方网文档,这里只会列举几个使用方法。

isNotNull-不为空

查询name不为空的用户,并且邮箱部位空的用户

1
2
3
4
5
6
7
8
9
10
@Test
public void isNotNullTest(){
// 查询name不为空的用户,并且邮箱部位空的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
// name不为null
wrapper.isNotNull("name");
// email部位null
wrapper.isNotNull("email");
userMapper.selectList(wrapper).forEach(System.out::println);
}

ge-大于等于

查询age大于等于22的用户

1
2
3
4
5
6
7
@Test
public void geTest(){
// 查询age大于等于22的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.ge("age",22);
userMapper.selectList(wrapper).forEach(System.out::println);
}

eq-等于

查询name等于Jone的用户

1
2
3
4
5
6
7
@Test
public void eqTest(){
// 查询name等于Jone的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","Jone");
System.out.println(userMapper.selectOne(wrapper));
}

selectOne:只查询一条数据

between-X与N之间的值

查询年龄在20到25之间的用户的数量

1
2
3
4
5
6
7
8
@Test
public void betweenTest(){
// 查询年龄在20到25之间的用户的数量
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,25);
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}

selectCount:查询数量

like-模糊查询

1
2
3
4
5
6
7
8
@Test
public void likeTest(){
// 查询name中包含a的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name","a");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

selectMaps:返回List<Map>形式的数据

notLike-模糊查询

查询name中不包含a的用户

1
2
3
4
5
6
7
8
@Test
public void notLikeTest(){
// 查询name中不包含a的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.notLike("name","a");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

likeLeft-左模糊查询

查询name中以e结尾的用户

1
2
3
4
5
6
7
8
@Test
public void likeLeftTest(){
// 查询name中以e结尾的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeLeft("name","e");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

likeRight-左模糊查询

查询name中以j开头的用户

1
2
3
4
5
6
7
8
@Test
public void likeRightTest(){
// 查询name中以j结开头的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.likeRight("name","j");
List<Map<String,Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

inSql

id 在子查询中查询出来

1
2
3
4
5
6
7
8
@Test
public void inSqlTest(){
// id 在子查询中查询出来
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id < 5");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}

selectObjs:返回List<Object>形式的数据

orderByDesc-降序排序

根据id进行降序排序

1
2
3
4
5
6
7
8
@Test
public void orderByDescTest(){
// 根据id进行降序排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> userList = userMapper.selectList(wrapper);
userList.forEach(System.out::println);
}

分页插件

MyBatisPlus也提供了分页插件,但是需要稍微的配置一下

在配置类中添加分页插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package cn.yanghuisen.mpdemo.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Y
* @date 2020/9/6 15:57
* @desc MyBatisPlus配置类
*/
@Configuration
public class MyBatisPlusConfig {

@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}

测试分页

1
2
3
4
5
6
7
8
@Test
void pageTest(){
// 第1页,5条数据
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);
page.getRecords().forEach(System.out::println);
log.info("总条数:"+page.getTotal());
}

代码生成器、

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

添加依赖

MyBatisPlus3.0.3版本之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖

1
2
3
4
5
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package cn.yanghuisen.mpdemo;

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 java.util.ArrayList;

/**
* @author Y
* @date 2020/9/6 19:01
* @desc 代码生成
*/
public class CodeGeneration {
public static void main(String[] args) {
// 构建一个代码生成器对象
AutoGenerator autoGenerator = new AutoGenerator();
// 配置策略
GlobalConfig gc = new GlobalConfig();
// 获取项目路径
String projectPath = System.getProperty("user.dir");
// 设置输出目录
gc.setOutputDir(projectPath+"src/main/java");
// 设置作者
gc.setAuthor("啊哈哈");
// 是否打开资源管理器,false,不打开
gc.setOpen(false);
// 是否覆盖
gc.setFileOverride(false);
// service名称
gc.setServiceName("%sService");
// ID类型
gc.setIdType(IdType.ASSIGN_ID);
// 日期类型
gc.setDateType(DateType.ONLY_DATE);

// 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mpdemo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
dsc.setDbType(DbType.MYSQL);

// 包的配置
PackageConfig pc = new PackageConfig();
// 设置模块名称
pc.setModuleName("demo");
// 设置包名
pc.setParent("cn.yanghuisen.mpdemo");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("t_user");
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// 自动lombok
strategy.setEntityLombokModel(true);
// 逻辑删除
strategy.setLogicDeleteFieldName("flag");

// 自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> fills = new ArrayList<>();
fills.add(createTime);
fills.add(updateTime);
strategy.setTableFillList(fills);

// 乐观锁
strategy.setVersionFieldName("version");

strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);

// 把全局配置设置到代码生成器中
autoGenerator.setGlobalConfig(gc);
// 把数据源设置到代码生成器中
autoGenerator.setDataSource(dsc);
// 把包配置设置到代码生成器中
autoGenerator.setPackageInfo(pc);
// 把策略配置设置到代码生成器中
autoGenerator.setStrategy(strategy);

// 执行
autoGenerator.execute();
}
}

多数据源(2020-10-18补充)

多数据源,顾名思义,就是配置多个数据来源。一般项目中都是使用一个数据源就足够了,但是也有例外的时候,有时候就需要根据不同的业务需求连接不同的数据库进行查询。

MyBatis-Plus中已经集成了多数据源的功能,只需要简单的配置就可以使用。

多数据源的使用

添加依赖

Mybatis-Plus虽然支持多数据源配置,但是没有集成到MyBatis-Plus的的依赖中,需要单独添加依赖

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.baomidou/dynamic-datasource-spring-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.2.1</version>
</dependency>

上面依赖是截止到我写多数据源这个文档的时候的最新的版本

配置数据源

修改application.yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring:
datasource:
dynamic:
primary: master # 设置默认的数据源
strict: false # 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源primary所设置的数据源
datasource: # 数据源
master:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mpdemo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
slave_1:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/slave_1?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8

@DS

注解 结果
没有注解 使用默认数据源
@DS("dsName") dsName为数据源名称

@DS注解就是切换数据源的关键,该注解可以声明在类和方法上。如果同时在类和方法上都声明了该注解,则方法上的优先于类上的。(建议只在service上设数据源)

切换数据源非常简单,只需要在service方法上添加@DS注解即可,并且传递数据源名称即可

例子1:

1
2
3
4
5
6
7
@Override
@DS("master") // 默认就是master数据源,可以省略
public List<User> master() {
List<User> userList = userMapper.selectList(null);
userList.forEach(x->log.info(x.toString()));
return userList;
}

例子2:

1
2
3
4
5
6
7
@Override
@DS("slave_1") // 切换至slave_1数据源
public List<User> slave_1() {
List<User> userList = userMapper.selectList(null);
userList.forEach(x->log.info(x.toString()));
return userList;
}

这样就完成了数据源的切换

方法内切换数据源

上面的数据源切换有一个问题,就是在一个方法内还是只能使用一个数据源,如果要在方法内切换到别的数据源是无法做到的。比如以下代码

1
2
3
4
5
6
7
8
9
10
@Override
@DS("slave_1") // 切换至slave_1数据源
public List<User> slave_1() {
List<User> userList = userMapper.selectList(null);
userList.forEach(x->log.info(x.toString()));
/*
脑补这里有段代码,要切换至master数据源
*/
return userList;
}

这种问题我在公司就遇到了,单使用@DS注解也是不行的。

这时就要用到DynamicDataSourceContextHolder类了,通过DynamicDataSourceContextHolder,在方法内切数据源

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
@DS("slave_1")
public List<User> test02() {
// 切换数据源,参数为数据源名称
DynamicDataSourceContextHolder.push("master");
List<User> userList = this.dsMaster();
// 移除数据源,数据源使用之后要移除掉,移除之后会恢复到@DS注解指定的数据源
DynamicDataSourceContextHolder.poll();
userList.forEach(x->log.info(x.toString()));
log.info("----切换数据源----");
userList = this.dsMaster();
userList.forEach(x->log.info(x.toString()));
return userList;
}

使用DynamicDataSourceContextHolder.push("master");切换到指定数据源,并且使用之后要使用DynamicDataSourceContextHolder.poll();恢复到@DS指定的数据源。

拓展

自动填充

在阿里巴巴开发手册中明确规定,所有的数据库表中都应该含有create_timeupdate_time两个字段(用于表示该条记录的创建和修改时间)。通过自动填充可以自动化的设置这两个字段的值,避免每次都要手动设置。自动填充有两种方式。

  • 方式一:数据库级别
  • 方式二:代码级别s

数据库级别

数据库级别的就很简单了,给表中添加create_timeupdate_time字段后,设置下就好了。不同的数据库工具设置方法不一样,这里以Navicat为例。

image.png

不建议使用这种方式,一般工作中不允许随意改变数据库

代码级别

注:代码级别设置自动填充的话,需要关闭数据库级别的自动填充(取消Navicat中的根据当前时间戳更新)

通过@TableField注解设置填充策略。

修改实体类,添加@TableField注解,设置填充策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
public class User {

@TableId(type = IdType.ASSIGN_ID)
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;
}

编写自动填充处理器类

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
package cn.yanghuisen.mpdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
* @author Y
* @date 2020/9/6 15:21
* @desc 自动填充处理器类
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

/**
* 插入时的填充策略
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}

/**
* 更新时的填充策略
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}

这样在进行插入和更新操作时,就可以自动填充时间到数据库了。

乐观锁

什么是乐观锁,什么是悲观锁

乐观锁,顾名思义,很乐观。就是它总会知道自己不会出现问题,无论干什么都不会上锁。如果出现问题,再次更新测试。

悲观锁,顾名思义,很悲观。就是它做什么都会上锁,再去操作。

乐观锁在数据库中一般使用version字段表示。

乐观锁的实现机制:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

乐观锁的实现

给数据库表添加version字段,默认值为1

实体类添加version属性,并给其添加@Version注解

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
package cn.yanghuisen.mpdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;

/**
* @author Y
* @date 2020/8/15 20:41
* @desc 用户实体类
*/
@TableName("user")
@Data
public class User {

@TableId(type = IdType.ASSIGN_ID)
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;

/**
* 乐观锁
*/
@Version
private Integer version;
}

创建配置类,注册乐观锁插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package cn.yanghuisen.mpdemo.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Y
* @date 2020/9/6 15:57
* @desc MyBatisPlus配置类
*/
@Configuration
public class MyBatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}

逻辑删除

物理删除:直接把数据中的数据删除

逻辑删除:在数据中没有被删除,而是通过一个变量(字段)来表示其失效(删除)状态

添加flag字段(用于表示是否是删除状态),类型为int,默认值为0

  • 0:没有删除
  • 1:已经删除

实体类中添加flag属性,并且设置注解@TableLogic

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
package cn.yanghuisen.mpdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;

/**
* @author Y
* @date 2020/8/15 20:41
* @desc 用户实体类
*/
@TableName("user")
@Data
public class User {

@TableId(type = IdType.ASSIGN_ID)
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;

/**
* 乐观锁
*/
@Version
private Integer version;

/**
* 逻辑删除
*/
@TableLogic
private Integer flag;
}

application.yml进行配置逻辑删除

1
2
3
4
5
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已经删除的值(默认为1)
logic-not-delete-value: 0 # 逻辑没有删除的值(默认为0)

测试逻辑删除

1
2
3
4
5
@Test
void deleteUserById(){
userMapper.deleteById(7L);
selectListTest();
}

注:在添加逻辑删除后,在查询时会自动过滤掉逻辑删除的记录。