一、首先,项目基于SpringCloud,配置文件在Git上(包括数据源的配置信息)。
二、开始基于原有项目进行重构
1、写一个动态数据源上下文.代码如下:
/**
* 动态数据源上下文
*/
public class DbContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
/*
* 管理所有的数据源id;
* 主要是为了判断数据源是否存在;
*/
public static List dataSourceIds = new ArrayList();
/**
* 设置数据源
* @param dbTypeEnum
*/
public static void setDbType(String dbTypeEnum) {
contextHolder.set(dbTypeEnum);
}
/**
* 取得当前数据源
* @return
*/
public static String getDbType() {
return (String)contextHolder.get();
}
/**
* 清除上下文数据
*/
public static void clearDbType() {
contextHolder.remove();
}
/**
* 判断指定DataSrouce当前是否存在
* @param dataSourceId
* @return
*/
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}
2、再写一个数据源路由类,代码如下:
/**
* 数据源路由类
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 取得当前使用哪个数据源
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
3、写一个自定义注解,用来传递需要哪个数据源:
/**
* 在方法上使用,用于指定使用哪个数据源
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
4、写一个AOP,只要有注解的就根据参数切换数据源:
@Component
@Order(-10)//保证该AOP在@Transactional之前执行
@Aspect
public class DBTypeAOP {
@Pointcut("@annotation(TargetDataSource)")
public void dbType() {
}
@Before("dbType()")
public void before(JoinPoint joinPoint) throws Throwable {
//获取当前的指定的数据源;
System.out.println("---------------before---------------");
TargetDataSource targetDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(TargetDataSource.class);
String dsId = targetDataSource.value();
//如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
if (!DbContextHolder.containsDataSource(dsId)) {
System.err.println("数据源[{}]不存在,使用默认数据源 > {}"+targetDataSource.value()+joinPoint.getSignature());
} else {
System.out.println("Use DataSource : {} > {}"+targetDataSource.value()+joinPoint.getSignature());
//找到的话,那么设置到动态数据源上下文中。
DbContextHolder.setDbType(targetDataSource.value());
}
}
@After("dbType()")
public void after(JoinPoint joinPoint) {
System.out.println("--------------after----------------");
TargetDataSource targetDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(TargetDataSource.class);
System.out.println("Revert DataSource : {} > {}"+targetDataSource.value()+joinPoint.getSignature());
//方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
DbContextHolder.clearDbType();
}
}
5、使用ImportBeanDefinitionRegistrar进行注册我们的多数据源:
/**
* 动态数据源注册;
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
//如配置文件中未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 默认数据源
private DataSource defaultDataSource;
private Map customDataSources = new HashMap();
/**
* 加载多数据源配置
*/
@Override
public void setEnvironment(Environment environment) {
System.out.println("DynamicDataSourceRegister.setEnvironment()");
initDefaultDataSource(environment);
initCustomDataSources(environment);
}
/**
* 加载主数据源配置.
* @param env
*/
private void initDefaultDataSource(Environment env){
// 读取主数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map dsMap = new HashMap();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
//创建数据源;
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
/**
* 初始化更多数据源
*/
private void initCustomDataSources(Environment env) {
// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
Map dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
/**
* 创建datasource.
* @param dsMap
* @return
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map dsMap) {
Object type = dsMap.get("type");
if (type == null){
type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
}
Class extends DataSource> dataSourceType;
try {
dataSourceType = (Class extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 为DataSource绑定更多数据
* @param dataSource
* @param env
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);//false
dataBinder.setIgnoreInvalidFields(false);//false
dataBinder.setIgnoreUnknownFields(true);//true
if(dataSourcePropertyValues == null){
Map rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map values = new HashMap<>(rpr);
// 排除已经设置的属性
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
Map
三、最后,我们在Application.java这个启动类中,使用@import注册DynamicDataSourceRegister:
@EnableDiscoveryClient
@SpringBootApplication
//注册
@Import({DynamicDataSourceRegister.class})
public class CircleOfFriendsApplication {
public static void main(String[] args) {
SpringApplication.run(CircleOfFriendsApplication.class, args);
}
}
四、运行:
执行的话,只需在你原有的方法上加上我们第3步写的自定义注解@TargetDataSource("数据源1")
例如:
五、效果:
---------------before---------------
Use DataSource : {} > {}ds1 com.luda.springcloud.client.controller.CircleOfFriendsController.queryCircleOfFriends(String,String,String)
2018-08-03 12:19:06.942 INFO [spring-cloud-circleOfFriends,e1c88ebca78edb0d,f27d8e29d1a698a3,false] 6708 --- [ XNIO-2 task-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
--------------after----------------
Time:5 ms - ID:com.luda.springcloud.client.mapper.CircleOfFriendsMapper.selectPage
Execute SQL:SELECT id,`createUser`,content,imgUrl,createTime,ip FROM circle_of_friends ORDER BY createTime DESC LIMIT 0,10
Revert DataSource : {} > {}ds1 com.luda.springcloud.client.controller.CircleOfFriendsController.queryCircleOfFriends(String,String,String)
进入了AOP,并且获取我需要的数据源,在方法完毕后,进行清除操作。
给我点个赞吧!拜托了^-^