当构建一个通用的数据访问模块的时候,一般需要处理分页,乐观锁、以及插入或者修改时一些公共字段默认值填充,以及慢查询处理定制化(虽然mysql自带的有这方面功能但是通知的灵活性不如自定义更实用)
处理分页、乐观锁
1 2 3 4 5 6 7 8 9
| @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //分页 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 乐观锁 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }
|
插入或者修改设置默认字段
首先定义基类BaseEntity,这里定义了createTime,updateTime,delFlag三个默认配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| @Data @SuperBuilder @NoArgsConstructor public class BaseEntity { /** * 创建时间 */ @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
/** * 修改时间 */ @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
/** * 删除标志 */ @TableField(fill = FieldFill.INSERT) private Integer delFlag; }
|
MetaObjectHandler接口是mybatisPlus为我们提供的的一个扩展接口,我们可以利用这个接口在我们插入或者更新数据的时候,为一些字段指定默认值。
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
| public class DailyMartMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "delFlag", Integer.class, DelEnum.NORMAL.code()); } @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } /** * 2023/10/28 调整 * Mybatis-plus的自动更新要求待更新的字段为null,否则不更新。这就导致select 后 更新时无法自动更新 updateTime */ @Override public MetaObjectHandler strictFillStrategy(MetaObject metaObject, String fieldName, Supplier<?> fieldVal) { Object obj = fieldVal.get(); if (Objects.nonNull(obj)) { metaObject.setValue(fieldName, obj); } return this; }
|
将MetaObjectHandler注入
1 2 3 4 5 6 7 8
| /** * 在 插入/更新 数据时自动填充默认值 */ @Bean public PomeloMetaObjectHandler myMetaObjectHandler() { return new PomeloMetaObjectHandler(); }
|
慢查询处理
mysql的慢查询可以通过很多地方拦截,比如数据库本身就可以通过配置来实现拦击并打印为日志,一部分开源的数据库连接池也可以实现,比如阿里的druid。这些虽然都可以实现慢查询拦截,但是这些属于第三方,不够灵活所以我们这里可以使用Mybatis提供的拦截器接口Interceptor来自己实现拦截,自己是实现时可以结合消息队列来完成阀值式处理,这些为后话,我们先来进行一个简单的实现
定义一个配置文件实体
1 2 3 4 5 6 7 8
| @ConfigurationProperties(prefix = "pomelo.sql.slow") @Data public class SlowSQLProperties {
private boolean enable = false;
private long cost = 1000L; }
|
实现Interceptor
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
| @Slf4j @Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}) }) @RequiredArgsConstructor public class SlowSqlInterceptor implements Interceptor { private final SlowSQLProperties slowSQLProperties; @Override public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); StopWatch stopWatch = new StopWatch(); stopWatch.start(); StatementHandler statementHandler = (StatementHandler) target; try { return invocation.proceed(); } finally { stopWatch.stop(); long costTime = stopWatch.getTotalTimeMillis();
if (costTime >= slowSQLProperties.getCost()) { BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
// 20230901 去掉sql中的换行符 sql = sql.replaceAll("\\n+", " ");
log.warn("WARN !!!,监测到慢查询SQL:[{}] 执行耗时 {} ms ", sql, costTime); }
} }
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
}
|
注入mybatis插件
1 2 3 4 5 6 7 8
| /** * 注入mybatis插件,可以统计SQL执行耗时 */ @Bean @ConditionalOnProperty(name = "pomelo.sql.slow.enable", havingValue = "true", matchIfMissing = true) public ConfigurationCustomizer mybatisConfigurationCustomizer() { return configuration -> configuration.addInterceptor(new SlowSqlInterceptor(slowSQLProperties)); }
|
替换ID生成器
在分布式ID一文中我们自定义了ID,这里修改一下mybatis的id的生成器
1 2 3 4 5 6 7 8 9 10 11
| public class IdGenerator implements IdentifierGenerator { @Override public Number nextId(Object entity) { return (Long)IDUtils.generateKey(); }
@Override public String nextUUID(Object entity) { return IDUtils.get32UUID(); } }
|
注入生成器
1 2 3 4 5 6 7
| /** * 替换Mybatis-plus的算法生成器 */ @Bean public IdGenerator identifierGenerator() { return new IdGenerator(); }
|
统一扫描地址
当我们使用mybatis时需要在每个服务启动类上都需要通过@MapperScan注解指定Mapper对象的包路径地址。如果各个模块的命名规则之间遵循统一的命名规范,那么我们可以在配置主类中直接设置Mapper对象的包路径地址,这样SpringBoot启动类就不再需要依赖此注解了