git地址 https://github.com/xiaogengi/dynamic-data-source.git
首先引入 AOP的jar
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
首先 配置文件 添加数据库基本信息 这里 navigationbar 和 blogcontent 分别是两个数据源
spring:
datasource:
navigationbar:
username: root
password: Root1234.
jdbc-url: jdbc:mysql://182.92.236.164:3306/xg-blog-navigation-bar?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
blogcontent:
username: root
password: Root1234.
jdbc-url: jdbc:mysql://182.92.236.164:3306/xg-blog-content?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
继承 AbstractRoutingDataSource 代理类,通过ThreadLocal 在获取当前线程的 数据源key,保证多线程下不会被覆盖
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final ThreadLocal<String> DATA_SOURCE = new ThreadLocal<>();
public DynamicDataSource(DataSource dataSource, Map<Object, Object> dataSourceMap){
// 默认使用的数据源
super.setDefaultTargetDataSource(dataSource);
// 多个数据源的map 。 master : jdbc:mysql://182.92.236.164:3306
// salve : jdbc:mysql://182.92.236.164:3307
super.setTargetDataSources(dataSourceMap);
super.afterPropertiesSet();
}
// 这个方法返回的是当前线程使用的数据源key
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource){
DATA_SOURCE.set(dataSource);
}
public static String getDataSource(){
return DATA_SOURCE.get();
}
public static void removeDataSource(){
DATA_SOURCE.remove();
}
}
创建一个枚举 用来存放数据源key
public enum DataSourceEnum {
NAVIGATION_BAR("navigationbar"),
BLOG_CONTENT("blogcontent");
private String key;
DataSourceEnum(java.lang.String key) {
this.key = key;
}
public String getKey() {
return key;
}
}
创建一个配置类, 把多个数据源加载进来,然后统一放入map中
@Configuration
public class DynamicDataSourceConfig {
@Bean("blogContentDataSource")
@ConfigurationProperties("spring.datasource.blogcontent")// 把配置文件中这个属性下的值获取到,并创建数据源
public DataSource blogContentDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("navigationBarDataSource")
@ConfigurationProperties("spring.datasource.navigationbar")
public DataSource navigationBarDataSource(){
return DataSourceBuilder.create().build();
}
@Primary // 主数据源,不加会报错
// 定义 bean name,如果使用了 SqlSessionFactory 则把当前 bean 赋值到数据源中
@Bean("dataSource")
//@Qualifier DataSource 多个对象,值是对象的beanName
public DynamicDataSource dataSource(@Qualifier("blogContentDataSource") DataSource blogContentDataSource ,@Qualifier("navigationBarDataSource") DataSource navigationBarDataSource){
Map<Object, Object> map = new HashMap<>();
map.put(DataSourceEnum.NAVIGATION_BAR.getKey(), navigationBarDataSource);
map.put(DataSourceEnum.BLOG_CONTENT.getKey(), blogContentDataSource);
return new DynamicDataSource(navigationBarDataSource, map);
}
}
创建一个注解,
@Retention(RetentionPolicy.RUNTIME)// 运行时使用
@Target({ElementType.METHOD}) // 方法级注解
public @interface DS {
DataSourceEnum name() default DataSourceEnum.NAVIGATION_BAR;
}
创建一个切面类。 通过注解进行切面,获取注解中的数据源key,进行set 放到 ThreadLock中
@Aspect
@Component
@Order(-1)
public class DataSourceAspect {
@Around("@annotation(com.xg.blog.gateway.datasource.annotation.DS)")
public Object around(ProceedingJoinPoint point) throws Throwable{
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
DS ds = method.getAnnotation(DS.class);
if(ds == null){
DynamicDataSource.setDataSource(DataSourceEnum.NAVIGATION_BAR.getKey());
System.out.println(Thread.currentThread().getName() + ": dataSource is a " + DataSourceEnum.NAVIGATION_BAR.getKey());
}else{
DynamicDataSource.setDataSource(ds.name().getKey());
System.out.println(Thread.currentThread().getName() + ": dataSource is a " + ds.name().getKey());
}
try {
return point.proceed();
} finally {
DynamicDataSource.removeDataSource();
System.out.println(Thread.currentThread().getName() + ": remove dataSource");
}
}
}
注意!!! 启动类一定要剔除DataSourceAutoConfigurant
然后通过@EnableAspectJAutoProxy 进行启动 AOP
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableAspectJAutoProxy
public class XgBlogGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(XgBlogGatewayApplication.class, args);
}
}
service中注解的使用
源码中 判断是否拓展了数据源,如果有就是用拓展的数据源,否则使用DynamicDataSource构造方法中setDefaultTargatDataSource中的数据源
service层测试
@Component
public class DataSourceTest {
@DS(name = DataSourceEnum.BLOG_CONTENT)
public void createBlogContentDataSource(){
System.out.println("createBlogContentDataSource ........");
}
@DS(name = DataSourceEnum.NAVIGATION_BAR)
public void createNavigationBarDataSource(){
System.out.println("createNavigationBarDataSource ........");
}
}
@SpringBootTest
class XgBlogGatewayApplicationTests {
@Autowired
private DataSourceTest dataSourceTest;
@Test
public void dataSourceTest(){
dataSourceTest.createBlogContentDataSource();
dataSourceTest.createNavigationBarDataSource();
}
}
mapper层测试