目录
Spring Boot + Spring JPA + JDBC + Druid实现动态数据源切换
AbstractRoutingDataSource源码分析
需求代码实现
DynamicDataSource
DBContextHolder
DruidDbConfig
DataSourceProperties
AllDataSourcesExecuteSQLDevice
application.properties
参考:https://blog.csdn.net/aiyo92/article/details/101064167#comments_12008727
最后:
总结:
背景:公司开发一个系统,需要满足根据传入的数据库信息和sql,对应的去该库中执行该sql。
关键点:spring-jdbc的AbstractRoutingDataSource抽象类。
属性:
targetDataSources: 目标数据源
defaultTargetDataSource: 默认目标数据源
resolvedDataSources: 存储是数据库标识和数据源的映射关系
方法:
determineCurrentLookupKey()
setTargetDataSources()
setDefaultTargetDataSource()
分析:
先看关系,AbstractRoutingDataSource继承AbstractDataSource,AbstractDataSource实现DataSource, 既然是实现DataSource接口那就应该有getConnection()这个方法,一看果然有
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
getConnection()方法内容也很简单,determineTargetDataSource().getConnection();
determineTargetDataSource(): 看名知意,确定目标数据DataSource,再从DataSource中获取Connection。
下面看determineTargetDataSource()方法:
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
// resolvedDataSources数据源是否为空
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 确定当前数据源的key
Object lookupKey = determineCurrentLookupKey();
// 从resolvedDataSources集合中根据key获取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 + "]");
}
return dataSource;
}
关键代码是determineCurrentLookupKey()方法:
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
@Nullable
protected abstract Object determineCurrentLookupKey();
这是一个抽象方法,翻译一下注释:确定当前的lookupKey。通常将其实现为检查线程绑定事务的上下文。允许任意key。返回的key需要匹配由resolveSpecifiedLookupKey()方法存储的lookup key类型。
继承AbstractRoutingDataSource,覆写determineCurrentLookupKey()
package com.zchi.multiple_datasources_dynamic_switch;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Map;
import java.util.Set;
/**
* @author zchi
* @version 1.0
* @description 动态数据源类
* @createdate 2021/2/4
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 存放动态数据源
*/
private Map
/**
* @author zchi
* @version 1.0
* @description 数据源切换类
* @createdate 2021/2/4
*/
public class DBContextHolder {
// 对当前线程的操作-线程安全的
private static final ThreadLocal contextHolder = new ThreadLocal();
// 调用此方法,切换数据源
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
// 获取数据源
public static String getDataSource() {
return contextHolder.get();
}
// 删除数据源
public static void clearDataSource() {
contextHolder.remove();
}
}
package com.zchi.multiple_datasources_dynamic_switch;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* @author zchi
* @version 1.0
* @description 数据源配置类 主要功能是创建系统数据源对象和动态数据源对象
* @createdate 2021/2/4
*/
@Configuration
public class DruidDbConfig {
@Autowired
private JpaProperties jpaProperties;
@Autowired
private DataSourceProperties basicProperties;
@Bean(name = "systemDataSource") // 声明其为Bean实例
@Primary // 在同样的DataSource中,首先使用被标注的DataSource
@ConfigurationProperties("spring.datasource.druid")
public DataSource dataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
// 將DataSourceProperties基本的屬性(driverClassName、url、username、password、type)塞进druidDataSource
afterPropertiesSet(druidDataSource);
return druidDataSource;
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(dataSource());
Map targetDataSources = new HashMap<>();
targetDataSources.put("systemDataSource", dataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
@Bean(name = "systemNamedJdbcTemplate")
@Primary
public NamedParameterJdbcTemplate systemNamedJdbcTemplate(@Qualifier("systemDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
@Bean(name = "systemJdbcTemplate")
@Primary
public JdbcTemplate systemJdbcTemplate(@Qualifier("systemDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "dynamicJdbcTemplate")
@Primary
public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "dynamicNamedJdbcTemplate")
public NamedParameterJdbcTemplate dynamicNamedJdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
@Bean(name = "entityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean entityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException {
return builder.dataSource(dataSource()).packages("com.dtsz.portalspringboot.pojo.model").properties(jpaProperties.getProperties()).build();
}
@Bean(name = "entityManager")
@Primary
public EntityManager entityManager(EntityManagerFactoryBuilder builder) throws SQLException {
return entityManageFactory(builder).getObject().createEntityManager();
}
@Bean(name = "dynamicEntityManageFactory")
public LocalContainerEntityManagerFactoryBean dynamicEntityManageFactory(EntityManagerFactoryBuilder builder) throws SQLException {
return builder.dataSource(dynamicDataSource()).packages("com.dtsz.portalspringboot.pojo.model").properties(jpaProperties.getProperties()).build();
}
@Bean(name = "dynamicEntityManage")
public EntityManager dynamicEntityManage(EntityManagerFactoryBuilder builder) throws SQLException {
return entityManageFactory(builder).getObject().createEntityManager();
}
/**
* 装配事务管理器 (name = "transactionManager")
* @return
*/
// @Bean(name="")
// public DataSourceTransactionManager jdbcTemplateTransactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
// return new DataSourceTransactionManager(dataSource);
// }
/**
* 將DataSourceProperties基本的屬性(driverClassName、url、username、password、type)塞进druidDataSource
* 跟着druid-spring-boot-starter的DruidDataSourceWrapper类学的,具体自己查看体会。
* @param druidDataSource
*/
private void afterPropertiesSet(DruidDataSource druidDataSource) {
if (druidDataSource.getUsername() == null) {
druidDataSource.setUsername(this.basicProperties.determineUsername());
}
if (druidDataSource.getPassword() == null) {
druidDataSource.setPassword(this.basicProperties.determinePassword());
}
if (druidDataSource.getUrl() == null) {
druidDataSource.setUrl(this.basicProperties.determineUrl());
}
if (druidDataSource.getDriverClassName() == null) {
druidDataSource.setDriverClassName(this.basicProperties.getDriverClassName());
}
}
}
package com.zchi.multiple_datasources_dynamic_switch;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Transient;
/**
* @author zchi
* @version 1.0
* @description 数据源配置,封装连接数据库的一些信息
* @createdate 2021/2/4
*/
@Data
@Getter
@Setter
public class DataSourceProperties {
private String id;
private String code;
private String name;
/**
* 名称
*/
@Column(name = "TEXT", length = 150)
private String text;
/**
* 数据库名称
*/
private String database;
/**
* 链接地址
*/
private String url;
private String username;
private String password;
private String dbdrive;
private Integer minActionNum;
private Integer maxActionNum;
// 连接最小存活时间 毫秒
private Integer minEvictableIdleTime = 300000;
// 连接最大存活时间,必须小于数据库的配置的时间毫秒
private Integer maxEvictableIdleTime = 900000;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
}
package com.zchi.multiple_datasources_dynamic_switch;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.driver.internal.spi.ConnectionPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
/**
* @author zchi
* @version 1.0
* @description 所有的数据源sql执行器
* @createdate 2021/2/4
*/
@Slf4j
@Component
@Transactional
public class AllDataSourcesExecuteSQLDevice {
@Autowired
@Qualifier("systemJdbcTemplate")
private JdbcTemplate systemJdbcTemplate;
@Autowired
@Qualifier("systemNamedJdbcTemplate")
private NamedParameterJdbcTemplate systemNamedJdbcTemplate;
@Autowired
@Qualifier("dynamicJdbcTemplate")
private JdbcTemplate dynamicJdbcTemplate;
@Autowired
@Qualifier("dynamicNamedJdbcTemplate")
private NamedParameterJdbcTemplate dynamicNamedJdbcTemplate;
@Autowired
@Qualifier("dynamicDataSource")
private DynamicDataSource dynamicDataSource;
/**
* 任意数据源做Select操作
*
* @param dataSourceProperties 数据源 若为null则使用系统数据源
* @param sql 要执行的sql :xxx 形式的占位符
* @param params 需要替换的参数
* @return
* @throws Exception
*/
public List
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zchi_test
spring.datasource.username=root
spring.datasource.password=nopassword
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.initial-size=1
spring.datasource.druid.max-active=10
spring.datasource.druid.min-idle=1
spring.datasource.druid.max-wait=-1
感谢上边博主的分享
本博客在该博主基础上优化了一下默认数据源的注入,默认数据源的druid配置直接在配置文件中配置,不用再修改DruidDbConfig.dataSource()
的代码。
DynamicDataSource.createDataSource()
异常时close()掉和DynamicDataSource.
delDatasources()
时close()掉。DruidDataSourceStatManager.removeDataSource(druidDataSource);
并没有真正的关闭连接导致数据库的session一直存在。