1、目标
底层适配多种数据库,实现简单切换就能实现数据库的迁移,对领域层往上无感知。
现实场景:
让一个系统可以平滑的部署到不同的数据库上,方便项目的交付,减少二次开发和项目交付的时间和人力成本。
2. 技术架构设计
2.1 整体架构图
+---------------------+
| Application | 应用层
+----------+----------+
|
+----------v----------+
| Domain Layer | 领域层
+----------+----------+
|
+----------v----------+
| Infrastructure Layer| 基础设施层
+----------+----------+
| | |
+----+--+ +--+---+ +--+---+
|MySQL | |Oracle| |PGSQL| 数据库适配层
+-------+ +------+ +------+
3. 关键实现方案
前置条件:
1)单表的操作全使用mybatisPlus来完成,复杂的在自定义sql
2)多表的操作也可以使用类似JOOQ的代码工具实现
更复杂的自定义sql写到resource下不同数据库对应的文件夹中。
1、sql映射文件的切换:
spring的配置文件中配置要使用的DB类型名称,利用mybatis或mybatisPlus的xml扫描路径,利用springEL动态扫描不同的路径sql(在resource 下不同数据库有对应名称的sql文件夹)。
例如:
spring:
datasource:
db-type: mysql
mybatis-plus:
mapper-locations: classpath*:mybatis/${spring.datasource.db-type}/**/*Mapper.xml
2、repositoryImpl的切换:
继续使用domain层的repository接口操作持久层,不同的数据库定义不同的实现类去做各自的实现。有个DataBaseConfig类,里面使用静态内部类定义不同的数据库启用condition(实现spring的Condition接口),然后配合spring的@Condition 注解实现创建使用不同的repositoryImpl
扩展:加个repository接口的抽象实现类,用于写公共的持久化方法,非公共的写到各自的实现类中。
/**
* 用户抽象实现类
* 包含通用的方法实现,具体数据库的特定方法由子类实现
*/
public abstract class AbstractUserRepositoryImpl implements UserRepository{
}
@Repository
@Conditional(DatabaseConfig.MysqlCondition.class)
public class MysqlUserRepositoryImpl extends AbstractUserRepositoryImpl {
@Autowired
private MySqlUserMapper mySqlUserMapper;
}
/**
* 达梦数据库的用户仓库实现
*/
@Repository
@Conditional(DatabaseConfig.DmCondition.class)
public class DmUserRepositoryImpl extends AbstractUserRepositoryImpl {
@Autowired
private DmUserMapper dmUserMapper;
}
/**
* 数据库配置类
* 实现数据库类型的条件判断,用于@Conditional注解
*/
public class DatabaseConfig {
/**
* 达梦数据库条件类
*/
public static class DmCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbType = context.getEnvironment().getProperty("spring.datasource.db-type", "dm");
return "dm".equalsIgnoreCase(dbType);
}
}
/**
* MySQL数据库条件类
*/
public static class MysqlCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String dbType = context.getEnvironment().getProperty("spring.datasource.db-type", "dm");
return "mysql".equalsIgnoreCase(dbType);
}
}
}
3、依赖的切换:
利用maven profiles 实现按需加载不同的驱动,避免随着后面兼容数据库的增多,导致打包后包体积过大。例如
<profiles>
<!-- 达梦数据库 -->
<profile>
<id>dm</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>8.1.2.141</version>
</dependency>
</dependencies>
</profile>
<!-- MySQL数据库 -->
<profile>
<id>mysql</id>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</profile>
<!-- Oracle数据库 -->
<profile>
<id>oracle</id>
<dependencies>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc11</artifactId>
<version>21.8.0.0</version>
</dependency>
</dependencies>
</profile>
</profiles>
4、使用
4.1 增加新的数据库实现
1)增加数据库类型枚举定义
2)增加repository的实现类并实现方法
3)增加对应的sql映射文件
4)增加对应的连接依赖和修改数据库连接的4个属性
4.2 切换到已有的数据库实现
1)配置文件中修改数据库类型名称
2)修改数据库连接的4个属性