1.设置多数据源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
read:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3310/jpgs?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&verifyServerCertificate=false&zeroDateTimeBehavior=CONVERT_TO_NULL&tinyInt1isBit=false&allowMultiQueries=true&useAffectedRows=true
username: root
password: 123456
write:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3310/jpgs?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&verifyServerCertificate=false&zeroDateTimeBehavior=CONVERT_TO_NULL&tinyInt1isBit=false&allowMultiQueries=true&useAffectedRows=true
username: root
password: 123456
2.编写配置类
public class DataSourceHolder {
public static final String WRITE_DATASOURCE = "write";
public static final String READ_DATASOURCE = "read";
private static final ThreadLocal<String> local = new ThreadLocal<>();
public static void putDataSource(String dataSource) {
local.set(dataSource);
}
public static String getDataSource() {
return local.get();
}
public static void clearDataSource() {
local.remove();
}
}
@Configuration
public class DataSourcePropertiesConfig {
@Primary
@Bean("writeDataSourceProperties")
@ConfigurationProperties("spring.datasource.druid.write")
public DataSourceProperties writeDataSourceProperties() {
return new DataSourceProperties();
}
@Bean("readDataSourceProperties")
@ConfigurationProperties("spring.datasource.druid.read")
public DataSourceProperties readDataSourceProperties() {
return new DataSourceProperties();
}
}
//import static com.hengtiansoft.cron.config.DataSourceHolder.READ_DATASOURCE;
//import static com.hengtiansoft.cron.config.DataSourceHolder.WRITE_DATASOURCE;
@Log4j2
@Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
@Resource(name = "writeDataSourceProperties")
private DataSourceProperties writeProperties;
@Resource(name = "readDataSourceProperties")
private DataSourceProperties readProperties;
@Resource
private Environment environment;
private final static String DATASOURCE = "spring.datasource.druid";
@Override
public void afterPropertiesSet() {
Binder binder = Binder.get(environment);
BindResult<DruidDataSource> bindResult = binder.bind(DATASOURCE, DruidDataSource.class);
DruidDataSource druidDataSource = bindResult.get();
DruidDataSource writeDataSource =
writeProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
DruidDataSource readDataSource =
readProperties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
DruidDataSource wDruidDataSource = copyBaseDruidDataSource(WRITE_DATASOURCE,druidDataSource, writeDataSource);
DruidDataSource rDruidDataSource = copyBaseDruidDataSource(READ_DATASOURCE,druidDataSource, readDataSource);
setDefaultTargetDataSource(wDruidDataSource);
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(WRITE_DATASOURCE, wDruidDataSource);
dataSourceMap.put(READ_DATASOURCE, rDruidDataSource);
setTargetDataSources(dataSourceMap);
super.afterPropertiesSet();
}
private static DruidDataSource copyBaseDruidDataSource(String dataSourceName,DruidDataSource druidDataSource, DruidDataSource newDataSource) {
String url = newDataSource.getUrl();
String username = newDataSource.getUsername();
String password = newDataSource.getPassword();
String driverClassName = newDataSource.getDriverClassName();
DruidDataSource retDruidDataSource = new DruidDataSource();
BeanUtils.copyProperties(druidDataSource, retDruidDataSource);
retDruidDataSource.setName(dataSourceName);
retDruidDataSource.setUrl(url);
retDruidDataSource.setUsername(username);
retDruidDataSource.setPassword(password);
retDruidDataSource.setDriverClassName(driverClassName);
return retDruidDataSource;
}
@Override
protected Object determineCurrentLookupKey() {
String key = DataSourceHolder.getDataSource();
log.debug("读写分离生效了=走的={}",key);
if (key == null) {
// default datasource
return WRITE_DATASOURCE;
}
if(key.equals(READ_DATASOURCE)){
DataSourceHolder.clearDataSource();
}
return key;
}
}
这时候,我们就可以在代码中做数据源的切换了。因为读取配置不包含其余配置,故我们这通过copyBaseDruidDataSource给其赋值(因配置文件结构不同,读者配置自己合适的读到对应数据即可)。注意:new DruidDataSource()必须在afterPropertiesSet方法中,不然无法走determineCurrentLookupKey方法
DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE);
4.我们在java使用mybatis时做一层代理,实现自动切换数据库
@Log4j2
@Component
@Intercepts({
@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
CacheKey.class, BoundSql.class})})
public class MybatisDataSourceInterceptor implements Interceptor {
private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";
@Override
public Object intercept(Invocation invocation) throws Throwable {
boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
log.debug("================="+synchronizationActive);
if(synchronizationActive){
log.debug("=================走了写");
DataSourceHolder.putDataSource(DataSourceHolder.WRITE_DATASOURCE);
return invocation.proceed();
}
Object[] objects = invocation.getArgs();
MappedStatement ms = (MappedStatement) objects[0];
if (!ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
DataSourceHolder.putDataSource(DataSourceHolder.WRITE_DATASOURCE);
return invocation.proceed();
}
if(ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
DataSourceHolder.putDataSource(DataSourceHolder.WRITE_DATASOURCE);
return invocation.proceed();
}
DataSourceHolder.putDataSource(DataSourceHolder.READ_DATASOURCE);
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
完成,小伙伴们可以手动尝试下。