spring项目中多数据源使用
业务复杂,涉及到多个库;读写分离(缓解数据库的读性能瓶颈)
2、实现原理(基于Spring)
spring-jdbc 模块提供了 AbstractRoutingDataSource,其内部持有一个DataSource集合,可以在运行时动态切换数据源。

应用直接操作的是AbstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由
AbstractRoutingDataSource从事先配置好的数据源(ds1、ds2)选择一个,来访问对应的数据库。
2.1、通过 AbstractRoutingDataSource 实现动态数据源
@Nullable private Map<Object, Object> targetDataSources; @Nullable private Object defaultTargetDataSource; @Nullable private Map<Object, DataSource> resolvedDataSources;
-
-
defaultTargetDataSource 标识默认的连接
-
resolvedDataSources这个数据结构是通过targetDataSources构建而来,存储结构也是数据库标识和数据源的映射关系

getConnection()方法如下:
public Connection getConnection() throws SQLException { return this.determineTargetDataSource().getConnection(); }
protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } else { return dataSource; } }
@Nullable protected abstract Object determineCurrentLookupKey();
AbstractRoutingDataSource 定义了一个模板方法供我们定制化实现,用来获取 DataSource 标识(可以是字符串、对象等)
所以到这里,我们只需实现 DynamicDataSource 继承 AbstractRoutingDataSource ,实现其 determineCurrentLookupKey()
总结:实现动态数据源只需做如下4步
1、定义AbstractRoutingDataSource实现类DynamicDataSource
2、初始化时为targetDataSources设置 不同数据源的DataSource和标识、及defaultTargetDataSource
3、在determineTargetDataSource中提供对应的数据源标识即可
4、切换数据源识即
2.2、示例
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource datasource1: jdbc‐url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEnco ding=UTF8&useSSL=false username: root password: 123456 initial‐size: 1 min‐idle: 1 max‐active: 20 test‐on‐borrow: true driver‐class‐name: com.mysql.cj.jdbc.Driver datasource2: jdbc‐url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEnco ding=UTF8&useSSL=false username: root password: 123456 initial‐size: 1 min‐idle: 1 max‐active: 20 test‐on‐borrow: true driver‐class‐name: com.mysql.cj.jdbc.Driver
2、动态数据源设置
@Configuration public class DynamicDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.datasource1") public DataSource firstDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.datasource2") public DataSource secondDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(5); targetDataSources.put(DataSourceNames.FIRST, firstDataSource); targetDataSources.put(DataSourceNames.SECOND, secondDataSource); return new DynamicDataSource(firstDataSource, targetDataSources); } }
3、DynamicDataSource 类实现
public class DynamicDataSource extends AbstractRoutingDataSource { /** * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。 * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。 */ private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /** * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好 * * @param defaultTargetDataSource 默认数据源 * @param targetDataSources 目标数据源 */ public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { CONTEXT_HOLDER.set(dataSource); } public static String getDataSource() { return CONTEXT_HOLDER.get(); } public static void clearDataSource() { CONTEXT_HOLDER.remove(); } }
(1)直接通过 setDataSource 在 threadLocal 中设置数据源标识(灵活,但需要在代码处手动设置)
(2)AOP + 自定义注解
不同业务的数据源,一般通过 AOP + 自定义注解动态切换数据源
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface WR { String value() default "W"; }
切面类:
@Component @Aspect public class DynamicDataSourceAspect { // 前置通知 @Before("within(com.tuling.dynamic.datasource.service.impl.*) && @annotation(wr)") public void before(JoinPoint joinPoint, WR wr){ System.out.println(wr.value()); } }
使用注解:
@Service public class FrendImplService implements FrendService { @Autowired FrendMapper frendMapper; @Override @WR("R") // 库2 public List<Frend> list() { return frendMapper.list(); } @Override @WR("W") // 库1 public void save(Frend frend) { frendMapper.save(frend); }
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, R
owBounds.class,ResultHandler.class})})
public class DynamicDataSourcePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0];
// 读方法
if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
DynamicDataSource.name.set("R");
} else {
// 写方法
DynamicDataSource.name.set("W");
}
// 修改当前线程要选择的数据源的key
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
}
@Override public void saveAll(Frend frend) { wtransactionTemplate.execute(wstatus ‐> { rtransactionTemplate.execute(rstatus ‐> { try{ saveW(frend); saveR(frend); return true; } catch (Exception e){ wstatus.setRollbackOnly(); rstatus.setRollbackOnly(); return false; } }); return true; }); }
(2)spring声明式事务
@Transactional(transactionManager = "wTransactionManager") public void saveAll(Frend frend) throws Exception { FrendService frendService = (FrendService) AopContext.currentProxy(); frendService.saveAllR(frend); } @Transactional(transactionManager = "rTransactionManager",propagation = Propagation.REQUIRES_NEW ) public void saveAllR(Frend frend) { saveW(frend); saveR(frend); }
基于 SpringBoot 的多数据源组件,功能强悍,支持 Seata 分布式事务。
-
支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
-
支持数据库敏感配置信息 加密 ENC()。
-
支持每个数据库独立初始化表结构schema和数据库database。
-
支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
-
支持 自定义注解 ,需继承DS(3.2.0+)。
-
提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
-
提供对MybatisPlus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
-
提供 自定义数据源来源 方案(如全从数据库加载)。
-
提供项目启动后 动态增加移除数据源 方案。
-
提供Mybatis环境下的 纯读写分离 方案。
-
提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
-
支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
-
提供 基于seata的分布式事务方案。
-
提供 本地多数据源事务方案。 附:不能和原生spring事务混用。
(1)引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic‐datasource‐spring‐boot‐starter</artifactId>
<version>${version}</version>
</dependency>
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解
@Service @DS("slave") public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; public List selectAll() { return jdbcTemplate.queryForList("select * from user"); } @Override @DS("slave_1") public List selectByCondition() { return jdbcTemplate.queryForList("select * from user where age >10"); } }
public class AService { @DS("a")//如果a是默认数据源则不需要DS注解。 @DSTransactional public void dosomething(){ BService.dosomething(); CService.dosomething(); } } public class BService { @DS("b") public void dosomething(){ //dosomething } } public class CService { @DS("c") public void dosomething(){ //dosomething } }
只要@DSTransactional注解下任一环节发生异常,则全局多数据源事务回滚。

浙公网安备 33010602011771号