大家好,我是飘渺。在今天的DDD与微服务系列文章中,我们将继续探讨一个关键主题:如何构建一个通用的数据持久化模块。
1. 概述
在DDD的四层架构中,基础设施层主要负责提供通用的技术能力,例如数据持久化、缓存、消息传输等基础设施服务。在DailyMart中,存在多个微服务,每个微服务的基础设施层都需要依赖 MySQL数据库
以及MyBatis-Plus这一ORM
框架来实现数据的持久化。
通常情况下,我们需要在每个微服务的各自基础设施层进行如下操作:
第一步:引入数据库访问的相关依赖,例如:
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
第二步:创建Mybatis的配置类,常见的如启用mybatis-plus的分页插件:
java
@Configuration
public class MybatisConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
第三步:在启动类上添加@MapperScan注解,指定扫描的Mapper类的包路径:
java
@MapperScan("com.jianzh5.dailymart.module.product.infrastructure.dao.mapper")
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class, args);
}
}
可以看到,每个微服务都需要进行这些重复操作,相当繁琐。在DailyMart中,我们创建一个公共的数据访问模块,将这些公共依赖和配置收敛到自定义模块中,这样所有的基础设施层都只需要依赖这一公共模块即可。
2. 自定义数据访问模块
首先,在dailymart-starter模块中再创建一个子模块dailymart-datasource-spring-boot-starter,用以承载通用数据库访问能力。
这里需要说明的是:在我们自定义公共模块时最好遵循SpringBoot的命名规则。SpringBoot官方的starter都是以spring-boot-starter-*
来命名的,* 代表任意一个特定的应用类型,比如Redis的spring-boot-starter-redis。而当我们自定义starter时不要使用spring-boot开头命名,而是应该以*-spring-boot-starter
这样的形式命名。
以下是自定义数据访问模块的实现过程以及启用的公用能力的步骤
2.1 引入数据库相关依赖
在这个通用模块中,我们同样只需要引入mybatis-plus-boot-starter
和mysql-connector-java
两个依赖即可,具体代码可以参考前文。
2.2 创建公共配置类
在SpringBoot2.7以后,需要在src/resources/META-INF/spring文件夹下创建一个名为org.springframework.boot.autoconfigure.AutoConfiguration.imports
的文件,并在其中导入基础配置类的访问路径,如下图所示:
2.3 公共字段自动设置默认值
在进行业务表设计时,每张表都会设置三个基础字段,分别是创建时间createTime,更新时间updateTime,删除标识delFlag。在添加数据时会将createTime设置成当前时间,同时将delFlag字段设置成正常,而在更新时将updateTime设置成当前时间。
这种操作我们可以利用Mybatis-plus提供的MetaObjectHandler接口来自动帮我们实现:
JAVA
public class DailyMartMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
this.strictInsertFill(metaObject, "delFlag", Integer.class, DelEnum.NORMAL.code());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
}
}
同时,我们还创建一个基础对象BaseDO,让基础设施层的DO对象继承此对象:
JAVA
public class BaseDO {
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 删除标识
*/
@TableField(fill = FieldFill.INSERT)
private Integer delFlag;
}
2.4 SQL执行时间拦截器
有时候,我们经常需要查看SQL的执行耗时用以确定是否有性能问题。在使用Mybatis的情况下,我们可以通过实现Mybatis提供的拦截器接口Interceptor来帮我们实现:
JAVA
@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 DailyMartSlowSQLProperties dailyMartSlowSQLProperties;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) target;
try {
return invocation.proceed();
} finally {
long costTime = System.currentTimeMillis() - startTime;
if (costTime >= dailyMartSlowSQLProperties.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);
}
}
java
@Bean
@ConditionalOnProperty(name = "dailymart.sql.slow.enable", havingValue = "true", matchIfMissing = true)
public ConfigurationCustomizer mybatisConfigurationCustomizer() {
return configuration -> configuration.addInterceptor(new SlowSqlInterceptor(dailyMartSlowSQLProperties));
}
2.5 统一扫描地址
之前说过,每个服务启动类上都需要通过@MapperScan注解指定Mapper对象的包路径地址。在DailyMart中,由于各模块之间遵循统一的命名规范,所以我们可以在配置主类中直接设置Mapper对象的包路径地址,这样SpringBoot启动类就不再需要依赖此注解了
JAVA
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
scannerConfigurer.setBasePackage("com.jianzh5.dailymart.module.*.infrastructure.dao.mapper");
return scannerConfigurer;
}
当然,为了启用这些能力,还需要在主配置类中DailyMartMybatisPlusAutoConfiguration
注入这些对象,具体可以参考源码。
3. 小结
以上就是在DailyMart中如何构建通用的数据持久化模块,以提高微服务架构的开发效率和可维护性。通过自定义Spring Boot Starter模块,我们成功简化了配置流程,减少了冗余工作,同时引入了性能监控机制,帮助开发人员更好地管理和优化微服务项目。统一管理Mapper对象的包路径也有助于降低代码维护的复杂性。
DDD&微服务系列源码已经上传至GitHub,如果需要获取源码地址,请关注公号 java日知录 并回复关键字 DDD 即可!