一共有俩中实现方案,最近方案一已经调通,记录如下:
难点:如何将动态数据源的注入到sqlSessionFactory中。
方案一的实现数据源的注入,依赖于注解@MapperScan,在这个注解中有下面的属性值,我们人为生成,然后注入进去。
sqlSessionFactoryRef
方案二的实现方式,学习我们的通用Mapper插件,采用实现接口
ImportBeanDefinitionRegistrar
的方式,直接注入一个数据源到IOC容器中,其他都无需修改。
代码如下:
// 实现动态数据源,都需要继承该接口,应用模板方法设计模式,
//由子类实现 路由数据源 的具体实现
package com.itw.learn.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHandler.getDataSourceType();
}
}
package com.itw.learn.config;
public class DataSourceContextHandler {
private static final ThreadLocal<String> context = new ThreadLocal();
public static void setDataSourceType(String type){
context.set(type);
}
public static Object getDataSourceType() {
//给默认值,如果取出来为null,则走主库,可以抽取到配置信息中,从配置文件查
return context.get()==null ? "primary" : context.get();
}
public static void clearDataSourceType(){
context.remove();
}
}
接下来就是定义切面,我们在目标方法执行前进行数据源的切换操作,方法执行完后,清除数据源类型。
package com.itw.learn.config;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDatasource {
String value() default "primary";
}
package com.itw.learn.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
// 该切面优先级高于事务,必须保证事务开启前,已确定数据源
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(dataSource)")
public void setTargetDatasource(JoinPoint point, TargetDatasource dataSource){
String value = dataSource.value();
if ("primary".equals(value)){
DataSourceContextHandler.setDataSourceType("primary");
}else if ("slave1".equals(value)){
DataSourceContextHandler.setDataSourceType("slave1");
}else {//如果是其他值,默认走写库
DataSourceContextHandler.setDataSourceType("primary");
}
}
/**
* 重置数据源
* @param point
* @param dataSource
*/
@After("@annotation(dataSource))")
public void restoreDataSource(JoinPoint point, TargetDatasource dataSource) {
DataSourceContextHandler.clearDataSourceType();
System.out.println("Restore DataSource to [" + DataSourceContextHandler.getDataSourceType()
+ "] in Method [" + point.getSignature() + "]");
}
}
接下来,就是注册我们的数据源和seqSessionFactory的操作:
package com.itw.learn.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
//重要的是这个将我们的sqlSessionFactory注入进去。
@Configuration
@MapperScan(basePackages = "com.itw.learn.dao", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 我们接口文件的地址
public class DynamicDataSourceConfig implements EnvironmentAware {
private Environment env;
//由于我使用的是yml文件的方式,在DataSourceBuilder中的properties属性没有赋值
//需要手动赋值
// 将这个对象放入Spring容器中
@Bean(name = "PrimaryDataSource")
public DataSource getDateSource1() {
return DataSourceBuilder
.create()
.driverClassName(env.getProperty("spring.datasource.primary.driver-class-name"))
.url(env.getProperty("spring.datasource.primary.url"))
.username( env.getProperty("spring.datasource.primary.username"))
.password(env.getProperty("spring.datasource.primary.password"))
.build();
}
@Bean(name = "SecondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave1")
public DataSource getDateSource2() {
return DataSourceBuilder
.create()
.driverClassName(env.getProperty("spring.datasource.slave1.driver-class-name"))
.url(env.getProperty("spring.datasource.slave1.url"))
.username( env.getProperty("spring.datasource.slave1.username"))
.password(env.getProperty("spring.datasource.slave1.password"))
.build();
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,
@Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {
//这个地方是比较核心的targetDataSource 集合是我们数据库和名字之间的映射
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put("primary", primaryDataSource);
targetDataSource.put("slave1", secondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
dataSource.setDefaultTargetDataSource(primaryDataSource);//设置默认对象
return dataSource;
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapping/*/*.xml"));//设置我们的xml文件路径
return bean.getObject();
}
@Override
public void setEnvironment(Environment environment) {
this.env = environment ;
}
}
目录结构: