一.新建springboot项目,引入相应的maven依赖。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
二.配置数据源
- 这里application.properties配置了三个数据源
spring.datasource.jdbc-url = jdbc:mysql://10.237.147.94:3306/gdz?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters = stat
spring.datasource.maxActive = 100
spring.datasource.initialSize = 10
spring.datasource.maxWait = 60000
spring.datasource.minIdle = 500
spring.datasource.world.jdbc-url = jdbc:mysql://172.25.62.101:3306/world?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.world.username = root
spring.datasource.world.password = root
spring.datasource.world.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.world.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.world.filters = stat
spring.datasource.world.maxActive = 100
spring.datasource.world.initialSize = 10
spring.datasource.world.maxWait = 60000
spring.datasource.world.minIdle = 500
spring.datasource.china.jdbc-url = jdbc:mysql://172.25.62.101:3306/china?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.china.username = root
spring.datasource.china.password = root
spring.datasource.china.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.china.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.china.filters = stat
spring.datasource.china.maxActive = 100
spring.datasource.china.initialSize = 10
spring.datasource.china.maxWait = 60000
spring.datasource.china.minIdle = 500
- 初始化数据源
package com.gdz.dynamicdatasource.config;
import com.gdz.dynamicdatasource.dynamic.DataSourceKey;
import com.gdz.dynamicdatasource.dynamic.DynamicRoutingDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: guandezhi
* @Date: 2019/1/4 12:02
*/
@Configuration
public class DynamicDataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource defaultDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.china")
public DataSource chinaDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.world")
public DataSource worldDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 核心动态数据源
*
* @return 数据源实例
*/
@Bean
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setDefaultTargetDataSource(defaultDataSource());
Map<Object, Object> dataSourceMap = new HashMap<>(3);
dataSourceMap.put(DataSourceKey.DB_USER, defaultDataSource());
dataSourceMap.put(DataSourceKey.DB_CHINA, chinaDataSource());
dataSourceMap.put(DataSourceKey.DB_WORLD, worldDataSource());
dataSource.setTargetDataSources(dataSourceMap);
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
//此处设置为了解决找不到mapper文件的问题
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
/**
* 事务管理
*
* @return 事务管理实例
*/
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
说明:其中的数据源枚举类型如下(有三种类型):
package com.gdz.dynamicdatasource.dynamic;
/**
* @Author: guandezhi
* @Date: 2019/3/3 10:40
*/
public enum DataSourceKey {
DB_USER,
DB_WORLD,
DB_CHINA
}
三.新建一个本地线程变量,用于数据源aop切换之后的存储。
package com.gdz.dynamicdatasource.dynamic;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: guandezhi
* @Date: 2019/3/3 10:38
*/
@Slf4j
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<DataSourceKey> currentDatesource = new ThreadLocal<>();
/**
* 清除当前数据源
*/
public static void clear() {
currentDatesource.remove();
}
/**
* 获取当前使用的数据源
*
* @return 当前使用数据源的ID
*/
public static DataSourceKey get() {
return currentDatesource.get();
}
/**
* 设置当前使用的数据源
*
* @param value 需要设置的数据源ID
*/
public static void set(DataSourceKey value) {
currentDatesource.set(value);
}
}
四.将本地线程变量中的数据源设置到spring数据源中
package com.gdz.dynamicdatasource.dynamic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @Author: guandezhi
* @Date: 2019/3/3 10:38
*/
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
log.info("当前数据源:{}", DynamicDataSourceContextHolder.get());
return DynamicDataSourceContextHolder.get();
}
}
说明:这是最关键的一步,数据源能否切换成功就在这。
五.自定义数据源注解,将注解用于需要改变数据源的方法上。
package com.gdz.dynamicdatasource.service;
import com.gdz.dynamicdatasource.annotation.DynamicDataSource;
import com.gdz.dynamicdatasource.mapper.ChinaMapper;
import com.gdz.dynamicdatasource.mapper.UserMapper;
import com.gdz.dynamicdatasource.mapper.WorldMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: guandezhi
* @Date: 2019/3/3 10:45
*/
@Service
@Slf4j
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private ChinaMapper chinaMapper;
@Autowired
private WorldMapper worldMapper;
@DynamicDataSource(dataSource = "user")
public String getUserName() {
String userName = userMapper.queryUserName(1);
log.info("username:{}", userName);
return userName;
}
@DynamicDataSource(dataSource = "china")
public String getChinaName() {
String chinaName = chinaMapper.queryCountryName(0);
log.info("chinaname:{}", chinaName);
return chinaName;
}
@DynamicDataSource(dataSource = "world")
public String getWorldName() {
String worldName = worldMapper.queryWorldName(7);
log.info("worldname:{}", worldName);
return worldName;
}
}
说明:这里的@DynamicDataSource为自定义注解,如下:
package com.gdz.dynamicdatasource.annotation;
import java.lang.annotation.*;
/**
* @Author: guandezhi
* @Date: 2019/3/3 10:35
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DynamicDataSource {
String dataSource() default "";
}
六.定义一个aop切面,将方法上标注的数据源设置到本地线程变量。
package com.gdz.dynamicdatasource.aspect;
import com.gdz.dynamicdatasource.annotation.DynamicDataSource;
import com.gdz.dynamicdatasource.dynamic.DataSourceKey;
import com.gdz.dynamicdatasource.dynamic.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Author: guandezhi
* @Date: 2019/3/3 10:22
*/
@Slf4j
@Aspect
@Component
@Order(-1)
public class DynamicDataSourceAspect {
@Around("@annotation(com.gdz.dynamicdatasource.annotation.DynamicDataSource)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
return doinvoke(pjp);
}
private Object doinvoke(ProceedingJoinPoint pjp) throws Throwable {
//设置数据源
setDynamicDataSource(pjp);
//执行目标方法
Object result = pjp.proceed();
//清空数据源
DynamicDataSourceContextHolder.clear();
return result;
}
private void setDynamicDataSource(ProceedingJoinPoint pjp) {
MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
Method targetMethod = methodSignature.getMethod();
DynamicDataSource dynamicDataSource = targetMethod.getAnnotation(DynamicDataSource.class);
if (dynamicDataSource != null) {
String dataSource = dynamicDataSource.dataSource();
if (StringUtils.isNotEmpty(dataSource)) {
if ("china".equals(dataSource)) {
DynamicDataSourceContextHolder.set(DataSourceKey.DB_CHINA);
} else if ("world".equals(dataSource)) {
DynamicDataSourceContextHolder.set(DataSourceKey.DB_WORLD);
}
}
}
log.info("当前aop数据源:{}", DynamicDataSourceContextHolder.get());
}
}
说明:这里必须要加注解@Order(-1),目的是让切面优先执行,否则有可能导致数据源设置不成功。
总结:
1.此方式有待进一步研究,经测试发现事务无法管理。
2.有时数据源切换容易失败,请谨慎使用