/**
-
读写库与只读库自动切换的Datasource定义。 当定义为@Transactional(readOnly = true) 时,自动切换到只读库。
-
Aws本身支持多个只读库的负载均衡,此处不做多个只读库的设计。
-
@date 2018/9/19
*/
@Slf4j
public class PendaRwDataSource extends AbstractRoutingDataSource {private final String DATASOURCE_DEFINE_ERROR_CODE = “ORM_DS_DEF_001”;
private final String DATASOURCE_DEFINE_ERROR_MSG = “Datasource key must be master or slave”;public static final String MASTER = “master”;
public static final String SLAVE = “slave”;private boolean readReplicaEnabled = false;
@Override
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
//当key不是master或者slave的时候,抛出异常;
for (Object key : targetDataSources.keySet()) {
String keyStr = (String) key;
if (!keyStr.equals(MASTER) && !keyStr.equals(SLAVE)) {
log.error(DATASOURCE_DEFINE_ERROR_CODE + " - " + DATASOURCE_DEFINE_ERROR_MSG);
throw new InternalServiceException(DATASOURCE_DEFINE_ERROR_CODE, DATASOURCE_DEFINE_ERROR_MSG);
}
}
// 当key 包含Slave时,标记可读到只读备份库。
if (targetDataSources.containsKey(SLAVE)) {
readReplicaEnabled = true;
}
super.setTargetDataSources(targetDataSources);
}@Override
protected Object determineCurrentLookupKey() {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly() && readReplicaEnabled) {
log.debug("-----------------------determineCurrentLookupKey is " + SLAVE);
return SLAVE;
}
log.debug("----------------------determineCurrentLookupKey is " + MASTER);
return MASTER;
}
}
使用 determineCurrentLookupKey 来判断使用的是主库还是从库
package com.pendanaan.configuration;
import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.pendanaan.core.spring.SpringContextHolder;
import com.pendanaan.datasource.PendaRwDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.HashMap;
import java.util.Map;
/**
-
@date 2018/9/3
*/
@Slf4j
@Configuration
public class RwDataSourceAutoConfiguration {@Bean
@ConditionalOnMissingBean
public SpringContextHolder initSpringContextHolder() {
return new SpringContextHolder();
}@Configuration
@ConditionalOnProperty(name = “spring.datasource.druid.slave.slave-enable”, havingValue = “true”)
public static class SlaveDataSourceAutoConfiguration {@Bean(name = "slave") @ConfigurationProperties("spring.datasource.druid.slave") public DruidDataSource dataSourceSlave() { log.info("initializing slave data source."); return new DruidDataSource(); }
}
/**
-
初始化自定义的StatFilter配置
-
@return StatFilter
*/
@Bean
public StatFilter initStatFilter() {
StatFilter statFilter = new StatFilter();
statFilter.setDbType(“mysql”);
statFilter.setLogSlowSql(true);
statFilter.setSlowSqlMillis(2000);
statFilter.setMergeSql(true);return statFilter;
}
/**
-
初始化自定义的WallFilter配置
-
@return WallFilter
*/
@Bean
public WallFilter initWallFilter() {
WallConfig config = new WallConfig();
config.setDropTableAllow(false);
config.setCreateTableAllow(false);
config.setAlterTableAllow(false);
config.setTruncateAllow(false);
config.setDeleteAllow(false);WallFilter wallFilter = new WallFilter();
wallFilter.setDbType(“mysql”);
wallFilter.setConfig(config);return wallFilter;
}
@Bean
public Slf4jLogFilter initSlf4jLogFilter() {
return new Slf4jLogFilter();
}@Primary
@Bean(name = “master”)
@ConfigurationProperties(“spring.datasource.druid.master”)
public DruidDataSource dataSourceMaster() {
return new DruidDataSource();
}@Bean(name = “rwDataSource”)
@DependsOn(“initSpringContextHolder”)
public PendaRwDataSource initPendaRwDataSource(@Qualifier(“master”) DruidDataSource master) {
PendaRwDataSource datasourceProxy = new PendaRwDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(PendaRwDataSource.MASTER, master);DruidDataSource slave = SpringContextHolder.getBean("slave", DruidDataSource.class); if (null != slave) { targetDataSources.put(PendaRwDataSource.SLAVE, slave); } datasourceProxy.setTargetDataSources(targetDataSources); datasourceProxy.setDefaultTargetDataSource(master); return datasourceProxy;
}
@Primary
@Bean
public DataSourceTransactionManager transactionManager(PendaRwDataSource DataSource) {
return new DataSourceTransactionManager(DataSource) {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 必须在开始事务前,将readOnly状态变更正确,
// 否则在PendaRwDataSource的determineCurrentLookupKey无法获取到争取的readOnly值
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
super.doBegin(transaction, definition);
}
};
} -
}
可知,通过spring的GenericBeanDefinition 和 读取properties的方法,以及多租户实现类,可以通过appname去设置数据源,
GenericBeanDefinition define = new GenericBeanDefinition();
define.setBeanClass(PendaRwDataSource.class);
关键点, 设置了默认数据源和 动态数据源, 之后就可以 通过 determineCurrentLookupKey , 来实现动态数据源