多数据源一致性事务解决方案

廉价感情. 提交于 2020-01-01 12:49:33

spring 多数据源配置

spring 多数据源配置一般有两种方案:

1、在spring项目启动的时候直接配置两个不同的数据源,不同的sessionFactory。在dao 层根据不同业务自行选择使用哪个数据源的session来操作。

2、配置多个不同的数据源,使用一个sessionFactory,在业务逻辑使用的时候自动切换到不同的数据源,有一个种是在拦截器里面根据不同的业务现切换到不同的datasource;有的会在业务层根据业务来自动切换。但这种方案在多线程并发的时候会出现一些问题,需要使用threadlocal等技术来实现多线程竞争切换数据源的问题。

 

【本文暂时只讨论第一种方案】

spring多事务配置主要体现在db配置这块,配置不同的数据源和不同的session

1、一下贴出 spring-db.xml配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

    <bean id="test1DataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
        <property name="driverClassName" value="${database.test1.driverClassName}" />
        <property name="url" value="${database.test1.url}" />
        <property name="username" value="${database.test1.username}" />
        <property name="password" value="${database.test1.password}" />
    </bean>
    
    <bean id="test2DataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
        <property name="driverClassName" value="${database.test2.driverClassName}" />
        <property name="url" value="${database.test2.url}" />
        <property name="username" value="${database.test2.username}" />
        <property name="password" value="${database.test2.password}" />
    </bean>
    
    <bean id="test1SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test1DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <bean id="test2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test2DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <bean id="test1TxManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="test1DataSource"></property>  
    </bean>  
    <bean id="test2TxManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="test2DataSource"></property>  
    </bean>  
  
    <tx:annotation-driven transaction-manager="test2TxManager" />
    <tx:annotation-driven transaction-manager="test1TxManager" />
    
</beans>
复制代码

 

2、dao层做了一个小的封装,将不同的SqlSessionFactory 注入到 SessionFactory,通过BaseDao来做简单的封装,封装不同库的基本增删改。dao实现层都集成于Basedao 这样的话,实现可以根据自己需要来选择不同的库来操作不同的内容。

session工厂

复制代码
package com.neo.dao;

import com.neo.entity.Entity;

public class BaseDao extends SessionFactory{
    
    
    public void test1Update(Entity entity) {
    this.getTest1Session().update(entity.getClass().getSimpleName()+".update", entity);
    }
    
    public void test2Update(Entity entity) {
       this.getTest2Session().update(entity.getClass().getSimpleName()+".update", entity);
       }
}
复制代码

BaseDao

复制代码
package com.neo.dao;

import com.neo.entity.Entity;

public class BaseDao extends SessionFactory{
    
    
    public void test1Update(Entity entity) {
    this.getTest1Session().update(entity.getClass().getSimpleName()+".update", entity);
    }
    
    public void test2Update(Entity entity) {
       this.getTest2Session().update(entity.getClass().getSimpleName()+".update", entity);
       }
}
复制代码

 

以上的配置在多数据源连接,正常的增删改都是没有问题的,但是遇到分布式的事务是就出问题:

测试代码:

复制代码
package com.neo.service.impl;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.neo.dao.UserDao;
import com.neo.dao.UserInformationsDao;
import com.neo.entity.UserEntity;
import com.neo.entity.UserInformationsEntity;
import com.neo.service.UserService;

@Service
public class UserServiceImpl implements UserService {
    
    @Resource UserDao userDao;
    @Resource UserInformationsDao userInformationsDao;

    @Override
    @Transactional
    public void updateUserinfo() {
    
    UserEntity user=new UserEntity();
    user.setId(1);
    user.setUserName("李四4");
    
    UserInformationsEntity userInfo=new UserInformationsEntity();
    userInfo.setUserId(1);
    userInfo.setAddress("陕西4");
    
    userDao.updateUser(user);
    userInformationsDao.updateUserInformations(userInfo);
    
    if(true){
        throw new RuntimeException("test tx ");
    }
    }
    
    

}
复制代码

 

在service添加事务后,更新完毕抛出异常,test2更新进行了回滚,test1 数据更新没有回滚。

 

解决方案添加分布式的事务,Atomikos和spring结合来处理。

Atomikos多数据源的配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

     <bean id="test1DataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="test1"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="url">${database.test1.url}</prop>
                <prop key="user">${database.test1.username}</prop>
                <prop key="password">${database.test1.password}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="borrowConnectionTimeout" value="30" />
        <property name="testQuery" value="select 1" />
        <property name="maintenanceInterval" value="60" />
    </bean>
    
     <bean id="test2DataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="test2"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="url">${database.test2.url}</prop>
                <prop key="user">${database.test2.username}</prop>
                <prop key="password">${database.test2.password}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="borrowConnectionTimeout" value="30" />
        <property name="testQuery" value="select 1" />
        <property name="maintenanceInterval" value="60" />
    </bean>
    
    <bean id="test1SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test1DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <bean id="test2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test2DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <!-- 分布式事务 -->
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
        <property name="forceShutdown" value="true"/>
    </bean>
    
    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300"/>
    </bean>
    
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager"/>
        <property name="userTransaction" ref="atomikosUserTransaction"/>
    </bean>
    
    <tx:annotation-driven/>
    
</beans>
复制代码

 

所有代码请参考这里:

https://github.com/ityouknow/spring-examples


作者:纯洁的微笑 
出处:www.ityouknow.com 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 

spring 多数据源配置

spring 多数据源配置一般有两种方案:

1、在spring项目启动的时候直接配置两个不同的数据源,不同的sessionFactory。在dao 层根据不同业务自行选择使用哪个数据源的session来操作。

2、配置多个不同的数据源,使用一个sessionFactory,在业务逻辑使用的时候自动切换到不同的数据源,有一个种是在拦截器里面根据不同的业务现切换到不同的datasource;有的会在业务层根据业务来自动切换。但这种方案在多线程并发的时候会出现一些问题,需要使用threadlocal等技术来实现多线程竞争切换数据源的问题。

 

【本文暂时只讨论第一种方案】

spring多事务配置主要体现在db配置这块,配置不同的数据源和不同的session

1、一下贴出 spring-db.xml配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

    <bean id="test1DataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
        <property name="driverClassName" value="${database.test1.driverClassName}" />
        <property name="url" value="${database.test1.url}" />
        <property name="username" value="${database.test1.username}" />
        <property name="password" value="${database.test1.password}" />
    </bean>
    
    <bean id="test2DataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" >
        <property name="driverClassName" value="${database.test2.driverClassName}" />
        <property name="url" value="${database.test2.url}" />
        <property name="username" value="${database.test2.username}" />
        <property name="password" value="${database.test2.password}" />
    </bean>
    
    <bean id="test1SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test1DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <bean id="test2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test2DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <bean id="test1TxManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="test1DataSource"></property>  
    </bean>  
    <bean id="test2TxManager"   class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="test2DataSource"></property>  
    </bean>  
  
    <tx:annotation-driven transaction-manager="test2TxManager" />
    <tx:annotation-driven transaction-manager="test1TxManager" />
    
</beans>
复制代码

 

2、dao层做了一个小的封装,将不同的SqlSessionFactory 注入到 SessionFactory,通过BaseDao来做简单的封装,封装不同库的基本增删改。dao实现层都集成于Basedao 这样的话,实现可以根据自己需要来选择不同的库来操作不同的内容。

session工厂

复制代码
package com.neo.dao;

import com.neo.entity.Entity;

public class BaseDao extends SessionFactory{
    
    
    public void test1Update(Entity entity) {
    this.getTest1Session().update(entity.getClass().getSimpleName()+".update", entity);
    }
    
    public void test2Update(Entity entity) {
       this.getTest2Session().update(entity.getClass().getSimpleName()+".update", entity);
       }
}
复制代码

BaseDao

复制代码
package com.neo.dao;

import com.neo.entity.Entity;

public class BaseDao extends SessionFactory{
    
    
    public void test1Update(Entity entity) {
    this.getTest1Session().update(entity.getClass().getSimpleName()+".update", entity);
    }
    
    public void test2Update(Entity entity) {
       this.getTest2Session().update(entity.getClass().getSimpleName()+".update", entity);
       }
}
复制代码

 

以上的配置在多数据源连接,正常的增删改都是没有问题的,但是遇到分布式的事务是就出问题:

测试代码:

复制代码
package com.neo.service.impl;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.neo.dao.UserDao;
import com.neo.dao.UserInformationsDao;
import com.neo.entity.UserEntity;
import com.neo.entity.UserInformationsEntity;
import com.neo.service.UserService;

@Service
public class UserServiceImpl implements UserService {
    
    @Resource UserDao userDao;
    @Resource UserInformationsDao userInformationsDao;

    @Override
    @Transactional
    public void updateUserinfo() {
    
    UserEntity user=new UserEntity();
    user.setId(1);
    user.setUserName("李四4");
    
    UserInformationsEntity userInfo=new UserInformationsEntity();
    userInfo.setUserId(1);
    userInfo.setAddress("陕西4");
    
    userDao.updateUser(user);
    userInformationsDao.updateUserInformations(userInfo);
    
    if(true){
        throw new RuntimeException("test tx ");
    }
    }
    
    

}
复制代码

 

在service添加事务后,更新完毕抛出异常,test2更新进行了回滚,test1 数据更新没有回滚。

 

解决方案添加分布式的事务,Atomikos和spring结合来处理。

Atomikos多数据源的配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

     <bean id="test1DataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="test1"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="url">${database.test1.url}</prop>
                <prop key="user">${database.test1.username}</prop>
                <prop key="password">${database.test1.password}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="borrowConnectionTimeout" value="30" />
        <property name="testQuery" value="select 1" />
        <property name="maintenanceInterval" value="60" />
    </bean>
    
     <bean id="test2DataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="uniqueResourceName" value="test2"/>
        <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"/>
        <property name="xaProperties">
            <props>
                <prop key="url">${database.test2.url}</prop>
                <prop key="user">${database.test2.username}</prop>
                <prop key="password">${database.test2.password}</prop>
            </props>
        </property>
        <property name="minPoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="borrowConnectionTimeout" value="30" />
        <property name="testQuery" value="select 1" />
        <property name="maintenanceInterval" value="60" />
    </bean>
    
    <bean id="test1SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test1DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <bean id="test2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="test2DataSource" />
        <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath*:mybatis/mapper/*.xml" />
    </bean>  
    
    <!-- 分布式事务 -->
    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
        <property name="forceShutdown" value="true"/>
    </bean>
    
    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300"/>
    </bean>
    
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager"/>
        <property name="userTransaction" ref="atomikosUserTransaction"/>
    </bean>
    
    <tx:annotation-driven/>
    
</beans>
复制代码

 

所有代码请参考这里:

https://github.com/ityouknow/spring-examples

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!