请参考两个代码:
下面是对guns框架中单数据源和多数据源的解析。对于多数据源的使用,guns与上面github中的代码类似,只是多增加了注解@DataSource("xxx")来显式说明哪一个数据源
spring boot中首先配置好数据库连接信息
spring:
profiles: local
datasource:
url: jdbc:mysql://127.0.0.1:3306/guns?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT
username: root
password: root
filters: wall,mergeStat
构造了一个druid属性类,用来填充datasource所需要的数据信息 ,并且此属性可以从application.properties文件中自动装配
public class DruidProperties {
private String url;
private String username;
private String password;
private String driverClassName;
private Integer initialSize = 2;
private Integer minIdle = 1;
private Integer maxActive = 20;
private Integer maxWait = 60000;
private Integer timeBetweenEvictionRunsMillis = 60000;
private Integer minEvictableIdleTimeMillis = 300000;
private String validationQuery = "SELECT 'x'";
private Boolean testWhileIdle = true;
private Boolean testOnBorrow = false;
private Boolean testOnReturn = false;
private Boolean poolPreparedStatements = true;
private Integer maxPoolPreparedStatementPerConnectionSize = 20;
private String filters = "stat";
public void config(DruidDataSource dataSource) {
dataSource.setUrl(this.url);
dataSource.setUsername(this.username);
dataSource.setPassword(this.password);
dataSource.setDriverClassName(this.driverClassName);
dataSource.setInitialSize(this.initialSize);
dataSource.setMinIdle(this.minIdle);
dataSource.setMaxActive(this.maxActive);
dataSource.setMaxWait((long)this.maxWait);
dataSource.setTimeBetweenEvictionRunsMillis((long)this.timeBetweenEvictionRunsMillis);
dataSource.setMinEvictableIdleTimeMillis((long)this.minEvictableIdleTimeMillis);
dataSource.setValidationQuery(this.validationQuery);
dataSource.setTestWhileIdle(this.testWhileIdle);
dataSource.setTestOnBorrow(this.testOnBorrow);
dataSource.setTestOnReturn(this.testOnReturn);
dataSource.setPoolPreparedStatements(this.poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(this.maxPoolPreparedStatementPerConnectionSize);
try {
dataSource.setFilters(this.filters);
} catch (SQLException var3) {
var3.printStackTrace();
}
}
主要是配置datasource的bean,另外一个DruidProperties实质上是为了构造datasource而需要的
/**
* druid配置
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidProperties druidProperties() {
return new DruidProperties();
}
/**
* 单数据源连接池配置
*/
@Bean
public DruidDataSource dataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
首先需要配置至少两个以上的不同的datasource的构造方法(下文配置了两个datasource,一个叫做datasource,另一个叫做bizDataSource)
/**
* guns的数据源
*/
private DruidDataSource dataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
/**
* 多数据源,第二个数据源
*/
private DruidDataSource bizDataSource(DruidProperties druidProperties,
MutiDataSourceProperties mutiDataSourceProperties) {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
mutiDataSourceProperties.config(dataSource);
return dataSource;
}
开始定义一个动态数据源的类 ,此类需要继承AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,方法返回的是一个数据源的字符串名称
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource() {
}
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
返回对应数据源的字符串名称是使用了 ThreadLocal方式进行线程副本的维护
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal();
public DataSourceContextHolder() {
}
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return (String)contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
接着需要配置真正的动态数据源DynamicDataSource的Bean,这里可以拿它跟刚刚上文的单一数据源DruidDataSource做对比
/**
* 多数据源连接池配置
*/
@Bean
public DynamicDataSource mutiDataSource(DruidProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
DruidDataSource dataSourceGuns = dataSource(druidProperties);
DruidDataSource bizDataSource = bizDataSource(druidProperties, mutiDataSourceProperties);
try {
dataSourceGuns.init();
bizDataSource.init();
} catch (SQLException sql) {
sql.printStackTrace();
}
DynamicDataSource dynamicDataSource = new DynamicDataSource();
HashMap
@Pointcut("@annotation(cn.stylefeng.roses.core.mutidatasource.annotion.DataSource)")
private void cut() { }
@Around("cut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Signature signature = point.getSignature();
MethodSignature methodSignature = null;
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
} else {
methodSignature = (MethodSignature)signature;
Object target = point.getTarget();
Method currentMethod = arget.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
DataSource datasource = (DataSource)currentMethod.getAnnotation(DataSource.class);
if (datasource != null) {
DataSourceContextHolder.setDataSourceType(datasource.name());
this.log.debug("设置数据源为:" + datasource.name());
} else {
DataSourceContextHolder.setDataSourceType(this.mutiDataSourceProperties.getDataSourceNames()[0]);
this.log.debug("设置数据源为:dataSourceCurrent");
}
Object var7;
try {
var7 = point.proceed();
} finally {
this.log.debug("清空数据源信息!");
DataSourceContextHolder.clearDataSourceType();
}
return var7;
}
}
/**
* 多数据源配置
*
* 注:由于引入多数据源,所以让spring事务的aop要在多数据源切换aop的后面
*
* @author stylefeng
* @Date 2017/5/20 21:58
*/
@Configuration
@ConditionalOnProperty(prefix = "guns.muti-datasource", name = "open", havingValue = "true")
@EnableTransactionManagement(order = 2, proxyTargetClass = true)
@MapperScan(basePackages = {"cn.stylefeng.guns.modular.*.dao", "cn.stylefeng.guns.multi.mapper"})
public class MultiDataSourceConfig {
将多数据源的aop的优先级调成1,则此优先级必定大于事务的优先级,说明会先切换数据源再进行事务的执行
@Aspect
public class MultiSourceExAop implements Ordered {
...
public int getOrder() {
return 1;
}
}