spring boot 多数据源切换aop---------------真的很方面

Boot版本 2.0

 

此处

@EnableTransactionManagement(order = 2)  需要打个问号,文章结尾会解释

application.yml

 

/**
 * 多数据源连接池配置
 */
@Bean
public DynamicDataSource mutiDataSource(DruidProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {

    DruidDataSource dataSourceGuns = dataSource(druidProperties);
    DruidDataSource bizDataSource = bizDataSource(druidProperties, mutiDataSourceProperties);

    try {
        dataSourceGuns.init();
        bizDataSource.init();
    } catch (SQLException sql) {
        sql.printStackTrace();
    }

    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    HashMap<Object, Object> hashMap = new HashMap<>();
    hashMap.put(mutiDataSourceProperties.getDataSourceNames()[0], dataSourceGuns);
    hashMap.put(mutiDataSourceProperties.getDataSourceNames()[1], bizDataSource);
    dynamicDataSource.setTargetDataSources(hashMap);
    dynamicDataSource.setDefaultTargetDataSource(dataSourceGuns);
    return dynamicDataSource;
}

 

/**
 * guns的数据源
 */
private DruidDataSource dataSource(DruidProperties druidProperties) {
    DruidDataSource dataSource = new DruidDataSource();
    druidProperties.config(dataSource);
    return dataSource;
}

/**
 * 多数据源,第二个数据源
 */
private DruidDataSource bizDataSource(DruidProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
    DruidDataSource dataSource = new DruidDataSource();
    druidProperties.config(dataSource);
    mutiDataSourceProperties.config(dataSource);
    return dataSource;
}

 

把初始参数赋值给 dataSource

@Bean
@ConfigurationProperties(prefix = "guns.muti-datasource")
public MutiDataSourceProperties mutiDataSourceProperties() {
    return new MutiDataSourceProperties();
}
import com.alibaba.druid.pool.DruidDataSource;

/**
 * 默认多数据源配置
 *
 */
public class MutiDataSourceProperties {

    private String url = "jdbc:mysql://127.0.0.1:3306/biz?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";

    private String username = "root";

    private String password = "root";

    private String driverClassName = "com.mysql.cj.jdbc.Driver";

    private String validationQuery = "SELECT 'x'";

    private String[] dataSourceNames = {"dataSourceGuns", "dataSourceBiz"};

    public void config(DruidDataSource dataSource) {
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driverClassName);
        dataSource.setValidationQuery(validationQuery);
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public String[] getDataSourceNames() {
        return dataSourceNames;
    }

    public void setDataSourceNames(String[] dataSourceNames) {
        this.dataSourceNames = dataSourceNames;
    }
}

需要改写一下

AbstractRoutingDataSource.java
public class DynamicDataSource extends AbstractRoutingDataSource {

   @Override
   protected Object determineCurrentLookupKey() {
      return DataSourceContextHolder.getDataSourceType();
   }

}

 

public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /**
     * 设置数据源类型
     *
     * @param dataSourceType 数据库类型
     */
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    /**
     * 获取数据源类型
     */
    public static String getDataSourceType() {
        return contextHolder.get();
    }

    /**
     * 清除数据源类型
     */
    public static void clearDataSourceType() {
        contextHolder.remove();
    }
}

其中

private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

是每一个线程的私有副本,不会造成脏读,但是这些副本变量必须要清除,防止内存泄漏。具体内存泄漏原理请参考

https://www.jianshu.com/p/dde92ec37bd1。添加一句 此处的内存泄漏一般只存在线程池中。

AOP

@Aspect
public class MultiSourceExAop implements Ordered {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    MutiDataSourceProperties mutiDataSourceProperties;

    @Pointcut(value = "@annotation(com.stylefeng.guns.core.mutidatasource.annotion.DataSource)")
    private void cut() {

    }

    @Around("cut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        Signature signature = point.getSignature();
        MethodSignature methodSignature = null;
        if (!(signature instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        methodSignature = (MethodSignature) signature;
        //通过反射获得方法
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(methodSignature.getName(),          methodSignature.getParameterTypes());
        //通过方法获得注解
        DataSource datasource = currentMethod.getAnnotation(DataSource.class);
        //通过注解 获得数据源Key
        if (datasource != null) {
            DataSourceContextHolder.setDataSourceType(datasource.name());
            log.debug("设置数据源为:" + datasource.name());
        } else {
            DataSourceContextHolder.setDataSourceType(mutiDataSourceProperties.getDataSourceNames()[0]);
            log.debug("设置数据源为:dataSourceCurrent");
        }

        try {
            return point.proceed();
        } finally {
            log.debug("清空数据源信息!");
            DataSourceContextHolder.clearDataSourceType();
        }
    }


    /**
     * aop的顺序要早于spring的事务
     */
    @Override
    public int getOrder() {
        return 1;
    }

}
/**
 * 
 * 多数据源标识
 *
 */
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface DataSource {

   String name() default "";
}
public interface DatasourceEnum {

   String DATA_SOURCE_GUNS = "dataSourceGuns";          //guns数据源
   
   String DATA_SOURCE_BIZ = "dataSourceBiz";        //其他业务的数据源
}
//客户端代码   -----完工哈哈哈
@DataSource(name = DatasourceEnum.DATA_SOURCE_GUNS)
@Transactional
public void testGuns() {
    Test test = new Test();
    test.setBbb("gunsTest");
    testMapper.insert(test);
}


为什么@EnableTransactionManagement(order = 2) 

因为切换数据源和开启事务管理是分先后的,spring事务管理是跟数据库事务绑定一起的,开启一个事务就已经和数据源绑定在一起,再切换数据源时,会造成切换数据源失效。所以切换数据源aop要在开启事务aop之前,所以切换数据源aop的order是1,事务的是2。

肥肠欢迎大家给我回复和提意见。我准备下一篇文章讨论下事务,这个事务感觉内容很复杂啊

 

 

 

 

 

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值