文章目录
MyBatis Plus 常用插件
一、逻辑删除
在 Spring Boot 中的 application.yml 文件中配置。如果我们保持默认,则此配置就不用写了。
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
然后在实体类上,添加 @TableLogic 注解。
@TableLogic
private Integer deleted;
【注意】使用 mp 自带方法删除和查找都会附带逻辑删除功能 ,但自己写的 xml 不会。
java 逻辑删除简单理解就是“删除”,它是为了方便数据恢复和保护数据本身价值等等的一种方案。如果这个数据你需要再去查出来,就不应该使用逻辑删除,而是以一个状态去表示。比如,员工离职、帐号锁定等都应该是一个状态字段,不适合使用逻辑删除。如果是要查找删除数据,则需要单独手写 SQL 来实现,比如需要查看过往所有数据的统计汇总信息。
二、通用枚举
主要是为了让 MyBatis 更优雅的使用枚举属性,抛弃那些超繁琐的配置。
从 3.1.1 版本开始,直接使用默认枚举类会更好。
推荐的配置方式:
1)使用 IEnum 接口,推荐配置 defaultEnumTypeHandler
2)使用注解枚举处理,推荐配置 typeEnumsPackage
3)注解枚举处理与 IEnum 接口,推荐配置 typeEnumsPackage
4)与原生枚举混用,需配置 defaultEnumTypeHandler 与 typeEnumsPackage
1. 使用通用枚举属性的两种方式
方式一:使用 @EnumValue 注解枚举属性
public enum GradeEnum {
PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中");
GradeEnum(int code, String descp) {
this.code = code;
this.descp = descp;
}
@EnumValue//标记数据库存的值是code
private final int code;
//。。。
}
方式二:枚举属性,实现 IEnum 接口
public enum AgeEnum implements IEnum<Integer> {
ONE(1, "一岁"),
TWO(2, "二岁"),
THREE(3, "三岁");
private int value;
private String desc;
@Override
public Integer getValue() {
return this.value;
}
}
实体属性使用枚举类型
public class User{
/**
* 名字
* 数据库字段: name varchar(20)
*/
private String name;
/**
* 年龄,IEnum接口的枚举处理
* 数据库字段:age INT(3)
*/
private AgeEnum age;
/**
* 年级,原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):
* 数据库字段:grade INT(2)
*/
private GradeEnum grade;
}
2. 配置扫描通用枚举
编辑 Spring Boot 项目 applicaton.yml 文件
mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.test.entity.enums
3. JSON 序列化处理
1)Jackson
在需要响应描述字段的 get 方法上添加 @JsonValue 注解即可
2)Fastjson
全局处理方式
FastJsonConfig config = new FastJsonConfig();
// 设置WriteEnumUsingToString config.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString);
converter.setFastJsonConfig(config);
局部处理方式
@JSONField(serialzeFeatures= SerializerFeature.WriteEnumUsingToString)
private UserStatus status;
二选一,然后在枚举中重写 toString() 方法。
三、 自动填充功能
在使用的时候,需要实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
1. 配置 @TableField 注解
注解填充字段 @TableField(.. fill = FieldFill.INSERT)
生成器策略部分也可以配置。
public class User {
// 注意!这里需要标记为填充字段
@TableField(.. fill = FieldFill.INSERT)
private String fillField;
....
}
自定义实现类,需要实现 MetaObjectHandler 接口,填充处理器在 Spring Boot 中使用的时候,需要再加上 @Component 注解。
必须使用父类的 setFieldValByName() 或者 setInsertFieldValByName / setUpdateFieldValByName 方法,否则不会根据注解中的 FiledFill.xxx 来区分。
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MyMetaObjectHandler.class);
@Override
public void insertFill(MetaObject metaObject) {
LOGGER.info("start insert fill ....");
this.setFieldValByName("operator", "Jerry", metaObject);//版本号3.0.6以及之前的版本
//this.setInsertFieldValByName("operator", "Jerry", metaObject);//@since 快照:3.0.7.2-SNAPSHOT, @since 正式版暂未发布3.0.7
}
@Override
public void updateFill(MetaObject metaObject) {
LOGGER.info("start update fill ....");
this.setFieldValByName("operator", "Tom", metaObject);
//this.setUpdateFieldValByName("operator", "Tom", metaObject);//@since 快照:3.0.7.2-SNAPSHOT, @since 正式版暂未发布3.0.7
}
}
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
四、SQL 注入器
注入器配置:全局配置 sqlInjector
用于注入 ISqlInjector
接口的子类,实现自定义方法注入。
自定义的通用方法可以实现接口 ISqlInjector
,也可以继承抽象类 AbstractSqlInjector
注入通用方法 SQL 语句
, 然后继承 BaseMapper
添加自定义方法,全局配置 sqlInjector
注入 MP 会自动将类所有方法注入到 mybatis
容器中。
public interface ISqlInjector {
/**
* 检查SQL是否注入(已经注入过不再注入)
*
* @param builderAssistant mapper 信息
* @param mapperClass mapper 接口的 class 对象
*/
void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}
五、攻击 SQL 阻断解析器
主要作用:阻止恶意的全表更新删除。
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
...
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
paginationInterceptor.setSqlParserList(sqlParserList);
...
return paginationInterceptor;
}
六、性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间。如果对 SQL 的打印效果要求较高,可以使用下面的【执行SQL分析打印】插件。
先配置 <plugins>
标签
<plugins>
....
<!-- SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 -->
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
<property name="maxTime" value="100" />
<!--SQL是否格式化 默认false-->
<property name="format" value="true" />
</plugin>
</plugins>
然后,在 Spring Boot 中使用
//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
/**
* SQL执行效率插件
*/
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor();
}
}
参数说明
-
参数:maxTime SQL 执行最大时长,超过自动停止运行,有助于发现问题。
-
参数:format SQL SQL是否格式化,默认false。
**【**注意】该插件只用于开发环境,不建议生产环境使用。
七、执行 SQL 分析打印
在 3.1.0+ 版本,该功能依赖 p6spy
组件,完美的输出打印 SQL 及执行时长。
1)在 Maven 中引入 p6spy 依赖
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>最新版本</version>
</dependency>
2)配置 application.yml 文件
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:h2:mem:test
...
3)配置 spy.properties 文件
module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,batch,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
【注意】
driver-class-name 为 p6spy 提供的驱动类。
url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址。
此插件有性能损耗,不建议生产环境使用。
八、乐观锁插件
1. 主要适用场景
意图:当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
2. 使用方式
插件配置
在 Spring Boot 中的配置
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
2)一定要在实体字段上使用 @Version 注解
@Version
private Integer version;
特别说明
- 支持的数据类型:int, Integer, long, Long, Date, Timestamp, LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法 - 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
示例
编写 Java 代码,具体如下:
int id = 100;
int version = 2;
User u = new User();
u.setId(id);
u.setVersion(version);
u.setXXX(xxx);
if(userService.updateById(u)){
System.out.println("Update successfully");
}else{
System.out.println("Update failed due to modified by others");
}
上面所对应的 SQL 原理非常简单:
update tbl_user set name = 'update',version = 3 where id = 100 and version = 2
九、动态数据源
1. 快速了解
1. 简介
dynamic-datasource-spring-boot-starter:一个基于 Springboot 的快速继承多数据源的启动器。
github 地址:https://github.com/baomidou/dynamic-datasource-spring-boot-starter
2. 优势
关于动态数据源的切换,核心只有两种。
1)构建多套环境,优势是方便控制也容易集成一些简单的分布式事务,缺点是非动态同时代码量较多,配置难度大。
2)基于spring提供原生的 AbstractRoutingDataSource
,参考一些文档自己实现切换。
如果你的数据源较少,场景不复杂,选择以上任意一种都可以。如果你需要更多特性,请尝试本动态数据源。
1)数据源分组,适用于多种场景、纯粹多库、读写分离、一主多从、混合模式。
2)简单集成 Druid 数据源监控多数据源,简单集成 Mybatis-Plus 简化单表,简单集成 P6sy 格式化 sql,简单集成 Jndi 数据源。
3)简化 Druid 和 HikariCp 配置,提供全局参数配置。
4)提供自定义数据源来源(默认使用 yml 或 properties 配置)。
5)项目启动后能动态增减数据源。
6)使用 spel 动态参数解析数据源,如从 session,header 和参数中获取数据源。(多租户架构神器)
7)多层数据源嵌套切换。(一个业务ServiceA调用ServiceB,ServiceB调用ServiceC,每个Service都是不同的数据源)
8)使用正则匹配或spel表达式来切换数据源(实验性功能)。
3. 劣势
不能使用多数据源事务(同一个数据源下能使用事务),网上其他方案也都不能提供。
如果你需要使用到分布式事务,那么你的架构应该到了微服务化的时候了。
4. 约定
1)本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何 CRUD。
2)配置文件所有以下划线 _
分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。
3)切换数据源即可是组名,也可是具体数据源名称,切换时默认采用负载均衡机制切换。
4)默认的数据源名称为 master ,你可以通过spring.datasource.dynamic.primary修改。
5)方法上的注解优先于类上注解。
5. 建议
强烈建议在 主从模式 下遵循普遍的规则,以便他人能更轻易理解你的代码。
主数据库 建议 只执行 INSERT
UPDATE
DELETE
操作。
从数据库 建议 只执行 SELECT
操作。
2. 具体使用
1)引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
2)配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
datasource:
master:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
slave_1:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
slave_2:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3308/dynamic
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
# 多主多从 纯粹多库(记得设置primary) 混合配置
spring: spring: spring:
datasource: datasource: datasource:
dynamic: dynamic: dynamic:
datasource: datasource: datasource:
master_1: mysql: master:
master_2: oracle: slave_1:
slave_1: sqlserver: slave_2:
slave_2: postgresql: oracle_1:
slave_3: h2: oracle_2:
3)使用 @DS 切换数据源。
@DS 可以注解在方法上和类上,同时存在方法注解优先于类上注解。
【注意】注解在 service 实现或 mapper 接口方法上,但强烈不建议同时在 service和 mapper 注解。 (可能会有问题)
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<Map<String, Object>> selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List<Map<String, Object>> selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
在 mybatis 环境下也可注解在 mapper 接口层。
@DS("slave")
public interface UserMapper {
@Insert("INSERT INTO user (name,age) values (#{name},#{age})")
boolean addUser(@Param("name") String name, @Param("age") Integer age);
@Update("UPDATE user set name=#{name}, age=#{age} where id =#{id}")
boolean updateUser(@Param("id") Integer id, @Param("name") String name, @Param("age") Integer age);
@Delete("DELETE from user where id =#{id}")
boolean deleteUser(@Param("id") Integer id);
@Select("SELECT * FROM user")
@DS("slave_1")
List<User> selectAll();
}
另可参考更多更细致的内容:https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter/wikis/pages
十、多租户 SQL 解析器
这里配合 分页拦截器 使用, spring boot 例子配置如下。
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
/*
* 【测试多租户】 SQL 解析处理拦截器<br>
* 这里固定写成住户 1 实际情况你可以从cookie读取,因此数据看不到 【 麻花藤 】 这条记录( 注意观察 SQL )<br>
*/
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override
public Expression getTenantId() {
return new LongValue(1L);
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean doTableFilter(String tableName) {
// 这里可以判断是否过滤表
/*
if ("user".equals(tableName)) {
return true;
}*/
return false;
}
});
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
// 过滤自定义查询此时无租户信息约束【 麻花藤 】出现
if ("com.baomidou.springboot.mapper.UserMapper.selectListBySQL".equals(ms.getId())) {
return true;
}
return false;
}
});
return paginationInterceptor;
}
十一、动态表明 SQL 解析
实现 ITableNameHandler 接口注入到 DynamicTableNameParser 处理器链中,将动态表名解析器注入到 MP 解析链。
【注意】原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换。例如:真实表名为 user 设定为 mp_dt_user 处理器替换为 user_2019 等。
十二、MyBatisX 快速开发插件
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx
搜索并安装。
项目码云地址:https://gitee.com/baomidou/MybatisX
功能:
1)Java 与 XML 调回跳转
2)Mapper 方法自动生成 MXL