上一篇文章《SpringBoot实现多数据源(二)【Mybatis插件】》
三、通过 AOP + 自定义注解切换多数据源
适用范围:不同场景下的业务,一般利用AOP,结合自定义注解动态切换数据源
- 配置文件
application.yml
不变,导入依赖- pom.xml
<dependencies>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
- 自定义读写分离注解
- @ReadAndWrite
package com.vinjcent.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE}) // 可以声明在哪些作用域上
@Retention(RetentionPolicy.RUNTIME)
/**
* SOURCE、CLASS、RUNTIME:
* SOURCE: 编译之后不会在target文件的.class中生成,无法通过反射获取
* CLASS: 会保留在.class中,不会被jvm加载
* RUNTIME: 运行时生效
*
*/
public @interface ReadAndWrite {
String value() default "write";
}
- 配置切面
- DynamicDataSourceAspect
package com.vinjcent.aspect;
import com.vinjcent.annotation.ReadAndWrite;
import com.vinjcent.config.DynamicDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 动态数据源切面
*/
@Component
@Aspect
public class DynamicDataSourceAspect {
// 前置通知before或环绕通知Around都可以
// @annotation(rw)为所有ReadAndWrite注解进行增强
// within(com.vinjcent.service.impl.*) 某个包下的类
@Before("within(com.vinjcent.service.impl.*) && @annotation(rw)")
public void before(JoinPoint point, ReadAndWrite rw) {
// 获取当前注解中的value值
String opt = rw.value();
// 根据opt修改ThreadLocal中name的值,更换动态数据源
DynamicDataSource.name.set(opt);
System.out.println(opt);
}
}
- 配置读写分离数据源
- DataSourceConfiguration
package com.vinjcent.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfiguration {
@Bean(name = "readDatasource")
@ConfigurationProperties(prefix = "spring.datasource.read")
public DataSource readDatasource() {
// 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "writeDatasource")
@ConfigurationProperties(prefix = "spring.datasource.write")
public DataSource writeDatasource() {
// 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
}
- 动态数据源配置
- DynamicDataSource
package com.vinjcent.config;
import com.vinjcent.constants.DataSourceConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Component
@Primary // 将该Bean设置为主要注入Bean
public class DynamicDataSource extends AbstractRoutingDataSource {
// 用于存储数据源的标识
public static ThreadLocal<String> name = new ThreadLocal<>();
// 写
private final DataSource writeDataSource;
// 读
private final DataSource readDataSource;
@Autowired
public DynamicDataSource(@Qualifier("readDatasource") DataSource readDataSource,
@Qualifier("writeDatasource") DataSource writeDataSource) {
this.readDataSource = readDataSource;
this.writeDataSource = writeDataSource;
}
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
// 初始化完bean之后调用该方法
@Override
public void afterPropertiesSet() {
// 为targetDataSources 初始化所有数据源
Map<Object, Object> sources = new HashMap<>();
sources.put(DataSourceConstants.READ_DATASOURCE, readDataSource);
sources.put(DataSourceConstants.WRITE_DATASOURCE, writeDataSource);
super.setTargetDataSources(sources);
// 为 defaultTargetDataSource 设置默认的数据源
super.setDefaultTargetDataSource(readDataSource);
// resolvedDataSources 负责最终切换的数据源map
super.afterPropertiesSet();
}
}
- Controller 层不变,修改impl中的 Service 层(这里就不再提供entity以及interface,与前面相同)
package com.vinjcent.service.impl;
import com.vinjcent.annotation.ReadAndWrite;
import com.vinjcent.mapper.PeopleMapper;
import com.vinjcent.pojo.People;
import com.vinjcent.service.PeopleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PeopleServiceImpl implements PeopleService {
private final PeopleMapper peopleMapper;
@Autowired
public PeopleServiceImpl(PeopleMapper peopleMapper) {
this.peopleMapper = peopleMapper;
}
@ReadAndWrite("read")
@Override
public List<People> list() {
return peopleMapper.list();
}
@ReadAndWrite("write")
@Override
public boolean save(People people) {
return peopleMapper.save(people);
}
}
- 运行并测试接口