spring 多数据源一致性事务方案

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);
       }
}

代码地址:https://github.com/ityouknow/spring-mybatis-mulidatasource/tree/master/spring-mybatis

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

测试代码:

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-mybatis-mulidatasource/tree/master/spring-mybatis-atomikos

时间: 2024-08-06 20:18:50

spring 多数据源一致性事务方案的相关文章

Spring多数据源分布式事务管理/springmvc+spring+atomikos[jta]+druid+mybatis

项目进行读写分离及分库分表,在一个业务中,在一个事务中处理时候将切换多个数据源,需要保证同一事务多个数据源数据的一致性.此处使用atomikos来实现:最后附源码: 1:spring3.0之后不再支持jtom[jta]了,第三方开源软件atomikos(http://www.atomikos.com/)来实现. 2:org.springframework.transaction.jta.JotmFactoryBean类,spring-tx-2.5.6.jar中有此类,spring-tx-3.0.

在Spring多数据源中事务管理

  今天在做Spring3+MyBatic3项目中,使用事务管理数据插入.但在异常情况下不回滚,找了会才发现原来是在配置事务管理器DataSourceTransactionManager时配置的数据源是另一个库的数据源. <bean id="transactionManagerLte" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <propert

spring多数据源分布式事务的分析与解决方案

一.概述 1.业务背景 对老系统进行重构合并,导致新系统需要同时对3个数据库进行管理.由于出现跨库业务,需要实现分布式事务. 2.开发环境 spring框架版本  4.3.10.RELEASE 持久层为结合mybatis写的领域模型,如 每一个entity对应数据库的一张表,@DataSource注解(自定义)了对应数据源的key值.所以一个业务中可能存在数据源的切换. 事务采用注解@Transaction驱动. 二.spring对多数据源的支持 spring框架通过抽象类AbstractRou

分布式事务最终一致性常用方案

目前的应用系统,不管是企业级应用还是互联网应用,最终数据的一致性是每个应用系统都要面临的问题,随着分布式的逐渐普及,数据一致性更加艰难,但是也很难有银弹的解决方案,也并不是引入特定的中间件或者特定的开源框架能够解决的,更多的还是看业务场景,根据场景来给出解决方案.根据笔者最近几年的了解,总结了几个点,更多的应用系统在编码的时候,更加关注数据的一致性,这样系统才是健壮的. 一.基础理论 目前关于事务的几大理论包括:ACID事务特性,CAP分布式理论,以及BASE等.ACID在数据库事务中体现,CA

spring + mybatis + 多数据源整合事务

1.核心思想,spring提供了一个DataSource的子类,该类支持多个数据源 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 该类的源码如下: org.springframework.jdbc.datasource.lookup; java.sql.Connection; java.sql.SQLException; java.util.HashMap; java.util.Iterator; jav

关于Spring Boot 多数据源的事务管理

自己的一些理解:自从用了Spring Boot 以来,这近乎零配置和“约定大于配置”的设计范式用着确实爽,其实对零配置的理解是:应该说可以是零配置可以跑一个简单的项目,因为Spring Boot 有默认的配置,当默认的配置满足不了你的时候,这时候所谓的零配置只是换了一种方式而已,我们都知道程序员最擅长的就是写代码,相比来说xml文件还是有点蛋疼的: 行啊,Spring Boot说你不是擅长写代码,那我就把配置给你转换为写代码的形式,所以你会发现在Spring Boot 中使用@Configura

spring 动态数据源

1.动态数据源:  在一个项目中,有时候需要用到多个数据库,比如读写分离,数据库的分布式存储等等,这时我们要在项目中配置多个数据库. 2.原理:   (1).spring 单数据源获取数据连接过程: DataSource --> SessionFactory --> Session  DataSouce   实现javax.sql.DateSource接口的数据源,  DataSource  注入SessionFactory, 从sessionFactory 获取 Session,实现数据库的

Spring的编程式事务和声明式事务

事务管理对于企业应用来说是至关重要的,当出现异常情况时,它也可以保证数据的一致性. Spring事务管理的两种方式 spring支持编程式事务管理和声明式事务管理两种方式. 编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager.对于编程式事务管理,spring推荐使用TransactionTemplate. 声明式事务是建立在AOP之上的.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法

spring的声明式事务

事务的传播特性 在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败.那么事务的传播特性也是从这里说起的.如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用