学习江南一点雨Tienchin项目学习之自定义多数据源注解。自定义注解后,可以实现将注解标注在类或者方法上,实现多数据源的切换。核心思想是,在标注的@DataSource注解的时候,那么就将该方法需要使用的数据源名称存入到 ThreadLocal。在后续的操作中去ThreadLocal中拿数据源的名称。
自定义数据源注解
DataSource注解
首先自定义一个注解,代码如下:
/**
* 定义一个注解,可以加在某一个service类或者方法上
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DataSource {
// 只有一个方法,指定数据源的名称
String value() default DataSourcetype.DEFAULT_DS_NAME;
}
然后定义一个切面,解析@DataSource 注解,当一个方法或者类上面有 @DataSource 注解的时候,将 @DataSource 注解所标记的数据源存入到 ThreadLocal 中。代码如下:
@Component
@Aspect
public class DataSourceAspect {
/**
* @annotation(com.changanw.dd.annotation.DataSource) 表示方法上有注解拦截下来
* @within(com.changanw.dd.annotation.DataSource) 表示类上有这个注解的话,把类中的方法拦截下来
*/
@Pointcut("@annotation(com.changanw.dd.annotation.DataSource) || @within(com.changanw.dd.annotation.DataSource)")
public void pc(){
}
@Around("pc()")
public Object around(ProceedingJoinPoint point){
//获取方法上的注解
DataSource dataSource = getDataSource(point);
if (dataSource != null){
final String value = dataSource.value();
DynamicDataSourceContextHolder.setDatasourceType(value);
}
try {
return point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
DynamicDataSourceContextHolder.clearDataSource();
}
return null;
}
private DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
//查找方法上的注解
DataSource annotation = AnnotationUtils.findAnnotation(methodSignature.getMethod(),DataSource.class);
if (annotation != null){
//说明方法上有DataSource注解
return annotation;
}
return AnnotationUtils.findAnnotation(methodSignature.getDeclaringType(),DataSource.class);
}
}
DataSource解析相关代码
1、DataSourceType 类型 常量,只指定了一个主数据源的名称
public interface DataSourcetype {
String DEFAULT_DS_NAME = "master";
}
2、DruidProperties 配置代码
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String type;
private String driverClassName;
private Map<String,Map<String,String>> ds;
private int initialSize;
private int minIdle;
private int maxActive;
private int maxWait;
/**
* 在外部构造一个DruidDataSource 对象,但是这个对象只包含三个重要属性,url username password
* 在这个方法里设置公共属性
* @param druidDataSource
* @return
*/
public DataSource dataSource(DruidDataSource druidDataSource){
druidDataSource.setInitialSize(initialSize);
druidDataSource.setMinIdle(minIdle);
druidDataSource.setMaxActive(maxActive);
druidDataSource.setMaxWait(maxWait);
return druidDataSource;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Map<String, Map<String, String>> getDs() {
return ds;
}
public void setDs(Map<String, Map<String, String>> ds) {
this.ds = ds;
}
public int getInitialSize() {
return initialSize;
}
public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public int getMaxActive() {
return maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxWait() {
return maxWait;
}
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
}
3、DynamicDatasource 类,继承自AbstractRoutingDataSource类,当 Mapper 执行的时候,需要 DataSource,他会自动去 AbstractRoutingDataSource 类中查找需要的数据源,我们只需要在 AbstractRoutingDataSource 中返回 ThreadLocal 中的值即可。
@Component
public class DynamicDatasource extends AbstractRoutingDataSource {
public DynamicDatasource(LoadDataSource loadDataSource) {
final Map<String, DataSource> dataSourceMap = loadDataSource.loadAllDatasource();
//1、设置所有的数据源
super.setTargetDataSources(new HashMap<>(dataSourceMap));
//2、设置默认数据源
super.setDefaultTargetDataSource(dataSourceMap.get(DataSourcetype.DEFAULT_DS_NAME));
//3
super.afterPropertiesSet();
}
/**
* 返回数据源名称,系统需要获取数据源时,会调用这个方法,获取数据源名称
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}
}
4、定义一个DynamicDataSourceContextHolder,用于存放当前使用注解上标注的数据源的名称,主要是一个ThreadLocal。
**
* 用例存放当前线程所使用的数据源名称
*/
public class DynamicDataSourceContextHolder {
private static ThreadLocal<String> DS_NAME = new ThreadLocal<>();
public static void setDatasourceType(String dsType){
DS_NAME.set(dsType);
}
public static String getDataSource(){
return DS_NAME.get();
}
public static void clearDataSource(){
DS_NAME.remove();
}
}
5、定义一个LoadDataSource,主要是加载配置文件中配置的数据源,将全部数据源加载到一个map中
@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {
@Autowired
DruidProperties druidProperties;
public Map<String, DataSource> loadAllDatasource(){
Map<String, DataSource> dataSourceMap = new HashMap<>();
final Map<String, Map<String, String>> ds = druidProperties.getDs();
try {
final Set<String> keySet = ds.keySet();
for (String s : keySet) {
dataSourceMap.put(s,druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(s))));
}
}catch (Exception e){
e.printStackTrace();
}
return dataSourceMap;
}
}
6、最后定义一个service类,并且标注上注解,准备一个测试数据,创建两个数据源,原先代码测试注解的运行情况。
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@DataSource("slave")
public List<User> getAllUser(){
return userMapper.getAllUser();
}
}
最后自定义的@DataSource 注解就完成了。完整的代码在 gitee.com/cnchangan/t… 可以去访问,然后拷贝下来运行一下,其中配置文件中的数据源需要换成自己的。