分布式事务、多数据源、分库分表中间件之spring boot基于Atomikos+XADataSource分布式事务配置(100%纯动态)

本文描述spring boot基于Atomikos+DruidXADataSource分布式事务配置(100%纯动态),也就是增加、减少数据源只需要修改application.properties文件,无需动态增加或减少Bean。

有时候我们一个应用会有N份部署,每个需要访问多个数据源,A环境可能只需要2个数据源,B环境需要5个数据源(因为我们是行业软件,所以会有这个情况,对于纯项目的系统,通常没有这个问题),所以我们希望代码只有一份,配置按需调整就确定了具体的数据源。

 MapperConfig配置:

package com.hundsun.ta.aop.config;

import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

@Configuration
public class MybatisConfig {

    @Order(1)
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer1() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" + "default");
        mapperScannerConfigurer.setBasePackage("com.hundsun.ta.base.mapper;com.hundsun.ta.aop.sysinfo.mapper;com.hundsun.ta.aop.parameters.mapper;com.hundsun.ta.aop.interfile.mapper;com.hundsun.ta.aop.demo.mapper;com.hundsun.ta.aop.config.mapper;com.hundsun.ta.aop.base.mapper;com.hundsun.ta.aop.auditresult.mapper;");
        return mapperScannerConfigurer;
    }
    
    @Order(2)
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer2() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" + "yoga1");
        mapperScannerConfigurer.setBasePackage("com.hundsun.ta.aop.ta.mapper;com.hundsun.ta.aop.ta.*.mapper");
        return mapperScannerConfigurer;
    }
    
    @Order(3)
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer3() {
        MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory" + "yoga2");
        mapperScannerConfigurer.setBasePackage("com.hundsun.ta.aop.yoga.*.mapper;com.hundsun.ta.aop.ta4.**.mapper");
        return mapperScannerConfigurer;
    }
}

XA配置

package com.hundsun.ta.datasource;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;

@ConfigurationProperties(prefix="dyn.spring")
@PropertySource("classpath:jrescloud.properties")
public class DynamicDataSourceConfig {
    
    private List<DataSource> datasources;
    
    public static class DataSource {
        private String name;
        private String driverClassName;
        private String url;
        private String username;
        private String password;
        private int maxActive;
        private int maxIdle;
        private String mapperLocations;
        private String basePackage;
        public int getMaxActive() {
            return maxActive;
        }
        public void setMaxActive(int maxActive) {
            this.maxActive = maxActive;
        }
        public int getMaxIdle() {
            return maxIdle;
        }
        public void setMaxIdle(int maxIdle) {
            this.maxIdle = maxIdle;
        }
        public int getMaxWait() {
            return maxWait;
        }
        public void setMaxWait(int maxWait) {
            this.maxWait = maxWait;
        }
        public String getValidationQuery() {
            return validationQuery;
        }
        public void setValidationQuery(String validationQuery) {
            this.validationQuery = validationQuery;
        }
        public boolean isDefaultAutoCommit() {
            return defaultAutoCommit;
        }
        public void setDefaultAutoCommit(boolean defaultAutoCommit) {
            this.defaultAutoCommit = defaultAutoCommit;
        }
        public String getConnectionInitSqls() {
            return connectionInitSqls;
        }
        public void setConnectionInitSqls(String connectionInitSqls) {
            this.connectionInitSqls = connectionInitSqls;
        }
        private int maxWait;
        private String validationQuery;
        private boolean defaultAutoCommit;
        private String connectionInitSqls;
        
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getDriverClassName() {
            return driverClassName;
        }
        public void setDriverClassName(String driverClassName) {
            this.driverClassName = driverClassName;
        }
        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 getMapperLocations() {
            return mapperLocations;
        }
        public void setMapperLocations(String mapperLocations) {
            this.mapperLocations = mapperLocations;
        }
        public String getBasePackage() {
            return basePackage;
        }
        public void setBasePackage(String basePackage) {
            this.basePackage = basePackage;
        }
    }

    public List<DataSource> getDatasources() {
        return datasources;
    }

    public void setDatasources(List<DataSource> datasources) {
        this.datasources = datasources;
    }
}
package com.hundsun.ta.datasource;

import java.util.Properties;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;

import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.hundsun.ta.datasource.DynamicDataSourceConfig.DataSource;

import oracle.jdbc.xa.client.OracleXADataSource;

@Service
public class DynamicDataSourceRegister implements InitializingBean,ApplicationContextAware,BeanPostProcessor {
    
    @Value("${dbType}")
    private String dbType;
    
    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;
    
    @Value("${mybatis.configLocation}")
    private String configLocation;
    
    @Value("${mybatis.typeAliasesPackage}")
    private String typeAliasesPackage;
    
    private ApplicationContext applicationContext;
    
    @Autowired
    private DynamicDataSourceConfig config;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        
//        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
        // 获取bean工厂并转换为DefaultListableBeanFactory
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
        for(DataSource datasource : config.getDatasources()) {
            RootBeanDefinition rbd = new RootBeanDefinition(AtomikosDataSourceBean.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, true);
            rbd.setInitMethodName("init");
            rbd.setDestroyMethodName("close");
//            
            MutablePropertyValues propertyValues = new MutablePropertyValues();
            /*propertyValues.add("url", datasource.getUrl());
            // propertyValues.add("url", "jdbc:mysql://" + app.getHostname() + ":" + app.getMapPort() + "/performance_schema?useUnicode=true&characterEncoding=gbk&autoReconnect=true&failOverReadOnly=false");
//            propertyValues.add("driverClassName", datasource.getDriverClassName());
            propertyValues.add("username", datasource.getUsername());
            propertyValues.add("password", datasource.getPassword());*/
//            propertyValues.add("password", Base64Util.getFromBase64(datasource.getPassword()));
            propertyValues.add("minPoolSize", 1);
            propertyValues.add("maxPoolSize", datasource.getMaxActive());
            propertyValues.add("borrowConnectionTimeout", datasource.getMaxWait());
//            propertyValues.add("maintenanceInterval", 30);
            propertyValues.add("xaDataSourceClassName", OracleXADataSource.class.getCanonicalName());
            propertyValues.add("uniqueResourceName","xa-" + datasource.getName());
            
            Properties xaProperties = new Properties();
            xaProperties.setProperty("URL", datasource.getUrl());
            xaProperties.setProperty("user", datasource.getUsername());
            xaProperties.setProperty("password", datasource.getPassword());
//            xaProperties.setProperty("testOnborrow", "true");
            propertyValues.add("xaProperties", xaProperties);
            rbd.setPropertyValues(propertyValues);
            rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
            beanFactory.registerBeanDefinition("xa-" + datasource.getName(), rbd);
//            targetDataSources.put(datasource.getName(), applicationContext.getBean(datasource.getName()));
            
            rbd = new RootBeanDefinition(SqlSessionFactoryBean.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, true);
            propertyValues = new MutablePropertyValues();
            propertyValues.add("configLocation", configLocation);
            propertyValues.add("mapperLocations", datasource.getMapperLocations());
            propertyValues.add("dataSource", applicationContext.getBean("xa-" + datasource.getName()));
            rbd.setPropertyValues(propertyValues);
            rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
            beanFactory.registerBeanDefinition("sqlSessionFactory" + datasource.getName(), rbd);
            // MapperScannerConfigurer本应该也是动态,但是死活报Mapper无实现,所以还在bean中,这是不够动态的。
/*            rbd = new RootBeanDefinition(MapperScannerConfigurer.class, AbstractBeanDefinition.AUTOWIRE_BY_NAME, true);
            propertyValues = new MutablePropertyValues();
            propertyValues.add("sqlSessionFactoryBeanName", "sqlSessionFactory" + datasource.getName());
            propertyValues.add("basePackage", datasource.getBasePackage());
            rbd.setPropertyValues(propertyValues);
            rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
            beanFactory.registerBeanDefinition("mapperScanner-" + datasource.getName(), rbd);*/
            
            propertyValues = new MutablePropertyValues();
            ConstructorArgumentValues cargs = new ConstructorArgumentValues();
            cargs.addIndexedArgumentValue(0, applicationContext.getBean("sqlSessionFactory" + datasource.getName()));
            rbd = new RootBeanDefinition(SqlSessionTemplate.class, cargs, propertyValues);
            rbd.setDependencyCheck(AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
            beanFactory.registerBeanDefinition("sqlSessionTemplate" + datasource.getName(), rbd);
        }
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException {
        return bean;
    }
}

配置文件:

dyn.spring.datasources[0].name=default
dyn.spring.datasources[0].driverClassName=oracle.jdbc.OracleDriver
dyn.spring.datasources[0].url=jdbc:oracle:thin:@10.20.39.223:1521:ora11g
dyn.spring.datasources[0].username=hs_aop
dyn.spring.datasources[0].password=hs_aop
dyn.spring.datasources[0].maxActive=100
dyn.spring.datasources[0].maxWait=5000
dyn.spring.datasources[0].maxIdle=10
dyn.spring.datasources[0].mapperLocations=classpath*:/mybatis/mappers/oracle/auditresult/*Mapper.xml
dyn.spring.datasources[0].basePackage=com.hundsun.ta.base.mapper;com.hundsun.ta.aop.sysinfo.mapper;com.hundsun.ta.aop.parameters.mapper;com.hundsun.ta.aop.interfile.mapper;com.hundsun.ta.aop.demo.mapper;com.hundsun.ta.aop.config.mapper;com.hundsun.ta.aop.base.mapper;com.hundsun.ta.aop.auditresult.mapper;
# 瑜伽TA分库
dyn.spring.datasources[1].name=yoga1
dyn.spring.datasources[1].driverClassName=oracle.jdbc.OracleDriver
dyn.spring.datasources[1].url=jdbc:oracle:thin:@10.20.39.223:1521:ora11g
dyn.spring.datasources[1].username=hs_aop
dyn.spring.datasources[1].password=hs_aop
dyn.spring.datasources[1].maxActive=100
dyn.spring.datasources[1].maxWait=5000
dyn.spring.datasources[1].maxIdle=10
dyn.spring.datasources[1].mapperLocations=classpath*:/mybatis/mappers/oracle/interfile/*Mapper.xml
dyn.spring.datasources[1].basePackage=com.hundsun.ta.aop.ta.mapper;

dyn.spring.datasources[2].name=yoga2
dyn.spring.datasources[2].driverClassName=oracle.jdbc.OracleDriver
dyn.spring.datasources[2].url=jdbc:oracle:thin:@10.20.39.223:1521:ora11g
dyn.spring.datasources[2].username=yoga2
dyn.spring.datasources[2].password=yoga2
dyn.spring.datasources[2].maxActive=100
dyn.spring.datasources[2].maxWait=5000
dyn.spring.datasources[2].maxIdle=10
dyn.spring.datasources[2].mapperLocations=classpath*:/mybatis/mappers/yoga2/**/*Mapper.xml
dyn.spring.datasources[2].basePackage=com.hundsun.ta.aop.yuga.mapper;

这样就支持分布式事务了,示例如下:

    @Transactional
    @Override
    public ResultModel<?> insert() {
        AgencyInfo agencyInfo = new AgencyInfo();
        agencyInfo.setAgencyName("4");
        agencyInfo.setAgencyNo("4");
        agencyInfo.setAgencyStatus("4");
        agencyInfo.setSysType("4");
        defaultDsMapper.insert(agencyInfo);
        
        SubDbInfo subDbInfo = new SubDbInfo();
        subDbInfo.setSubDbDataSource("4");
        subDbInfo.setSubDbNo("4");
        subDbInfo.setSysType("4");
        // 非独立事务
        taDsMapper.insert(subDbInfo);
        
        List insertList = new ArrayList();
        insertList.add("aaa");
//数据源使用SqlSessionTemplate动态切换 baseBatchMapper.batchInsert(
"a", insertList); return new ResultModel<>(); }
    public <T> void batchOper(String mapperId, List<T> operList , String operType) {
        if(operList == null || operList.isEmpty()) {
            logger.info("无需要批量入库的记录!");
            return;
        }
// 动态传入数据源即可 sqlSessionTemplate
= SpringContextHolder.getBean("sqlSessionTemplate" + "default",SqlSessionTemplate.class); SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);

XA存在的一个问题是事务传播级别REQUIRE_NEW不生效(还没找到怎么解决),如下

    /**
     * 暂时不支持自治事务
     */
    @Transactional
    @Override
    public ResultModel<?> insertAuto() {
        AgencyInfo agencyInfo = new AgencyInfo();
        agencyInfo.setAgencyName("2");
        agencyInfo.setAgencyNo("2");
        agencyInfo.setAgencyStatus("2");
        agencyInfo.setSysType("2");
        
        defaultDsMapper.insert(agencyInfo);
        SubDbInfo subDbInfo = new SubDbInfo();
/*        subDbInfo.setSubDbDataSource("2");
        subDbInfo.setSubDbNo("2");
        subDbInfo.setSysType("2");*/
        //独立事务,会报错,但是整个回滚了
        service.insertNew(subDbInfo);
        
        agencyInfo.setAgencyName("3");
        agencyInfo.setAgencyNo("3");
        agencyInfo.setAgencyStatus("3");
        agencyInfo.setSysType("3");
        defaultDsMapper.insert(agencyInfo);
        return new ResultModel<>();
    }
-- 加上rollbackFor,或者抛出RuntimeException都不行,整个XA被回滚了
    @Transactional
    public ResultModel<?> insertNew(SubDbInfo subDbInfo) {
        taDsMapper.insert(subDbInfo);
        return new ResultModel<>();
    }

使用druid xa数据源有一个问题,jstack会看到在获取连接的地方一直WAITING:

"http-nio-8080-exec-54" daemon prio=10 tid=0x0000000000e61000 nid=0xcc9 waiting on condition [0x00007f4a753d4000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000007a143f230> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
	at com.alibaba.druid.pool.DruidDataSource.takeLast(DruidDataSource.java:1732)
	at com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1330)
	at com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1198)
	at com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:4619)

换成OracleXA就没有问题,使用的druid是1.1.10,所以应该不是早期版本bug的问题。

猜你喜欢

转载自www.cnblogs.com/zhjh256/p/10406914.html
今日推荐