十二弹:在微服务中构建一个统一的数据处理模块(SpringBoot Starter)

大家好,我是飘渺。在今天的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-startermysql-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 即可!

相关推荐
AI人H哥会Java2 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱2 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-2 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu
中國移动丶移不动3 小时前
Java 并发编程:原子类(Atomic Classes)核心技术的深度解析
java·后端
Q_19284999064 小时前
基于Spring Boot的旅游推荐系统
spring boot·后端·旅游
愤怒的代码4 小时前
Spring Boot对访问密钥加密解密——RSA
java·spring boot·后端
美美的海顿4 小时前
springboot基于Java的校园导航微信小程序的设计与实现
java·数据库·spring boot·后端·spring·微信小程序·毕业设计
愤怒的代码4 小时前
Spring Boot中幂等性的应用
java·spring boot·后端
xiaocaibao7775 小时前
编程语言的软件工程
开发语言·后端·golang
0wioiw05 小时前
Flask-----SQLAlchemy教程
后端·python·flask