解决jta分布式事务控制下数据源无法动态切换的问题

本文介绍了一种在Java应用中实现动态数据源切换的方法,包括使用注解切换数据源、YAML配置数据源、创建数据源及配置多数据源等步骤,并通过自定义事务管理器确保事务一致性。
部署运行你感兴趣的模型镜像
  1. 使用注解切换数据源
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Datasource {
    /**
    • 数据源的值
    • @return
      */
      String value() default “”;
      }
  2. yml配置数据源
    datasource:
    #主数据源
    rch:
    jdbcUrl: jdbc:mysql://localhost:3306/master?relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&allowMultiQueries=true&tinyInt1isBit=false
    username: root
    password: 123456
    #从数据源
    talent:
    username: root
    password: 123456
    jdbcUrl: jdbc:mysql://localhost:3306/slave?relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&allowMultiQueries=true&tinyInt1isBit=false
  3. 数据源注入
    @Data
    public class MysqlConfig {
    private String jdbcUrl;
    private String username;
    private String password;
    }

/**
* 获取主数据源配置
* @return
*/
@Bean(“masterConfig”)
@ConfigurationProperties(“datasource.rch”)
public MysqlConfig rchConfig(){
return new MysqlConfig();
}

/**
 * 获取slave数据源配置
 * @return
 */
@Bean("slaveConfig")
@ConfigurationProperties("datasource.talent")
public  MysqlConfig talentConfig(){
    return  new MysqlConfig();
}



/**
 * master数据源
 * @return
 */
@Bean("master")
public DataSource rchDataSource(@Qualifier("masterConfig") MysqlConfig rchConfig) {
    log.info("rchDataSource init!");
    return createAtomikosDataSource(rchConfig,"rchHikariCP");

}
/**
 * slave数据源
 * @return
 */
@Bean("slave")
public DataSource talentDataSource(@Qualifier("slaveConfig") MysqlConfig talentConfig) {
    log.info("talentDataSource init!");
    return createAtomikosDataSource(talentConfig,"talentHikariCP");
}

/**
* 生成Atomikos数据源
* @param mysqlConfig
* @param poolName
* @return
*/
private DataSource createAtomikosDataSource(MysqlConfig mysqlConfig,String poolName){
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setUrl(mysqlConfig.getJdbcUrl());
mysqlXADataSource.setUser(mysqlConfig.getUsername());
mysqlXADataSource.setPassword(mysqlConfig.getPassword());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
// 事务管理器
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
atomikosDataSourceBean.setUniqueResourceName(poolName);
//连接池中最大的连接数
atomikosDataSourceBean.setMaxPoolSize(60);
atomikosDataSourceBean.setBorrowConnectionTimeout(120);
atomikosDataSourceBean.setMaxIdleTime(3600);
atomikosDataSourceBean.setReapTimeout(300);
atomikosDataSourceBean.setMaxLifetime(60000);
atomikosDataSourceBean.setMaintenanceInterval(60);
atomikosDataSourceBean.setTestQuery(“select 1”);

    return atomikosDataSourceBean;
}
  1. 重写AbstractRoutingDataSource 类,修改数据源路由方式
    @Slf4j
    public class HandleDataSource extends AbstractRoutingDataSource {

    public static final ThreadLocal holder = new ThreadLocal();

    /*默认数据源/
    private static DataSource defaultDataSource;

    /*设置默认数据源/
    @Override
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
    super.setDefaultTargetDataSource(defaultTargetDataSource);
    if (defaultTargetDataSource instanceof DataSource){
    defaultDataSource=(DataSource)defaultTargetDataSource;
    }
    }

    /**

    • 返回默认数据源
    • @return
      */
      public static DataSource getDefaultTargetDataSource(){
      return defaultDataSource;
      }

    public static void setDataSource(String dataSource){
    log.info(“设置了数据源{}”,dataSource);
    holder.set(dataSource);
    }

    public static String getDataSource(){
    String dataSource = holder.get();
    log.info(“获取了数据源{}”,dataSource);
    return dataSource;
    }
    public static void clearDataSource(){
    log.debug(“清除数据源了,恢复默认数据源!”);
    holder.remove();
    }

    @Override
    protected Object determineCurrentLookupKey() {
    return getDataSource();
    }
    }

  2. 设置注解切换切面处理
    @Aspect
    @Slf4j
    @Component
    @Order(-1)
    public class HandlerDataSourceAop {
    //@within在类上设置
    //@annotation在方法上进行设置
    @Pointcut("@within(com.yjh.jta.annonation.Datasource)||@annotation(com.yjh.jta.annonation.Datasource)")
    public void pointcut() {
    }

    @Before(“pointcut()”)
    public void doBefore(JoinPoint joinPoint) {
    Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    Datasource annotationClass = method.getAnnotation(Datasource.class);//获取方法上的注解
    if (annotationClass == null) {
    annotationClass = joinPoint.getTarget().getClass().getAnnotation(Datasource.class);//获取类上面的注解
    if (annotationClass == null) return;
    }
    //获取注解上的数据源的值的信息
    String dataSourceKey = annotationClass.value();
    if (!StringUtils.isEmpty(dataSourceKey)) {
    //给当前的执行SQL的操作设置特殊的数据源的信息
    HandleDataSource.setDataSource(dataSourceKey);
    }
    log.info(“AOP动态切换数据源,className” + joinPoint.getTarget().getClass().getName()
    + “methodName” + method.getName() + “;dataSourceKey:” + dataSourceKey);
    }

    @After(“pointcut()”)
    public void after(JoinPoint point) {
    //清理掉当前设置的数据源,让默认的数据源不受影响
    HandleDataSource.clearDataSource();

    }

  3. 配置多数据源
    @Primary
    @Bean(“dynamicDataSource”)
    public HandleDataSource multipleDataSource(@Qualifier(“master”) DataSource master,
    @Qualifier(“slave”) DataSource slave) {
    HandleDataSource handleDataSource = new HandleDataSource();
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put(“master”, master);
    //targetDataSources.put(DataSourceEnum.firm.getValue(), firmDataSource);
    targetDataSources.put(“talent”, slave);
    handleDataSource.setDefaultTargetDataSource(master);
    handleDataSource.setTargetDataSources(slave);
    //添加数据源
    return handleDataSource;
    }
    设置sqlsessionfactory
    @Bean(“sqlSessionFactory”)
    @Primary
    public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(@Autowired HandleDataSource dynamicDataSource) throws Exception {
    MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
    sessionFactory.setDataSource(dynamicDataSource);
    //设置我们自己重写的事务管理器,这里如果不设置,在是默认数据源没有添加注解时会获取数据源氏空的,会导致事务内切换数据源失败
    //sessionFactory.setTransactionFactory(new MyTransactionsFactory());
    sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(“classpath:/config/common-mybatis-config.xml”));
    sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
    return sessionFactory;
    }

/**
* 注入事物管理器
* @return
*/
@Bean(name = “jtaTransaction”)
@Primary
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
7. 测试:
@Override
//@Transactional(transactionManager = “jtaTransaction”)
//@Transactional(transactionManager = “masterTransaction”)
//@Transactional
public void update() {
//默认数据源
Department department = departmentMapper.selectCompanyById(“1”);
log.info(“department{}”,department);
//从数据源
int n = talentService.updateTalentStatus(“1330461138895355905”, 5);
//int i=7/0;
//默认数据源,主数据源,没有加注解
departmentMapper.updateDept(“产品研发中心”,“1”);

}

在我们没加注解@Transitonal()时能正常切换数据源
在这里插入图片描述
加入注解:发现数据源切换成从数据源后,被清除后事务中的数据源其实还是主数据源,就会报主库中没有改数据表
在这里插入图片描述

  1. 针对以上问题可添加一个自定义的事务管理
    public class MyManagedTransaction extends SpringManagedTransaction {

    DataSource dataSource;
    ConcurrentHashMap<String, Connection> map = new ConcurrentHashMap<>();

    public MyManagedTransaction(DataSource dataSource) {
    super(dataSource);
    this.dataSource = dataSource;
    }

    @Override
    public Connection getConnection() throws SQLException {
    String key = HandleDataSource.getDataSource();
    //String key = DynamicDataSourceContextHolder.getDateSourceType();
    if(key==null){
    Connection con = dataSource.getConnection();
    map.put(“default”, con);
    return con;
    }
    if (map.containsKey(key)) {
    return map.get(key);
    }
    Connection con = dataSource.getConnection();
    map.put(key, con);
    return con;
    }
    }

然后添加到事务管理工厂
public class MyTransactionsFactory extends SpringManagedTransactionFactory {

@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new MyManagedTransaction(dataSource);
}

}

  1. 最后需要加入到我们的session工厂内就是在第六点内的一行代码
    sessionFactory.setTransactionFactory(new MyTransactionsFactory());
    这里就能在事务中来回切换数据源而且也能保证事务的一致性,你们可以通过自己手动写异常测试。
    这里也是参考别人的写法:当然在这也有自己调整的地方
    转载:https://blog.youkuaiyun.com/weixin_42324471/article/details/106264883

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值