多数据源 spring怎么管理事务的
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多数据源 spring怎么管理事务的相关的知识,希望对你有一定的参考价值。
在SpringSide 3 中,白衣提供的预先配置好的环境非常有利于用户进行快速开发,但是同时也会为扩展带来一些困难。最直接的例子就是关于在项目中使用多个数据源的问题,似乎 很难搞。在上一篇中,我探讨了SpringSide 3 中的数据访问层,在这一篇中,我立志要解决多数据源配置的难题,我的思路是这样的:第一步、测试能否配置多个DataSource
第二步、测试能否配置多个SessionFactory
第三步、测试能否配置多个TransactionManager
第四步、测试能否使用多个TransactionManager,也就是看能否配置多个
基本上到第四步就应该走不通了,因为Spring中似乎不能配置多个,而且@transactional注解也无法让用户选择具体使用哪个TransactionManager。也就是说,在SpringSide的应用中,不能让不同的数据源分别属于不同的事务管理器,多数据源只能使用分布式事务管理器,那么测试思路继续如下进行:
第五步、测试能否配置JTATransactionManager
如果到这一步,项目还能顺利在Tomcat中运行的话,我们就算大功告成了。但我总认为事情不会那么顺利,我总觉得JTATransactionManager需要应用服务器的支持,而且需要和JNDI配合使用,具体是不是这样,那只有等测试后才知道。如果被我不幸言中,那么进行下一步:
第六步、更换Tomcat为GlassFish,更换JDBC的DataSource为JNDI查找的DataSource,然后配置JTATransactionManager
下面测试开始,先假设场景,还是继续用上一篇中提到的简单的文章发布系统,假设该系统运行一段时间后非常火爆,单靠一台服务器已经无法支持巨大的用户数, 这时候,站长想到了把数据进行水平划分,于是,需要建立一个索引数据库,该索引数据库需保存每一篇文章的Subject及其内容所在的Web服务器,而每 一个Web服务器上运行的项目,需要同时访问索引数据库和内容数据库。所以,需要创建索引数据库,如下:
[java] view plain copy
create database puretext_index;
use puretext_index;
create table articles(
id int primary key auto_increment,
subject varchar(256),
webserver varchar(30)
);
第一步测试,配置多个DataSource,配置文件如下:
application.properties:
[java] view plain copy
jdbc.urlContent=jdbc:mysql://localhost:3306/PureText useUnicode=true&characterEncoding=utf8
jdbc.urlIndex=jdbc:mysql://localhost:3306/PureText_Index useUnicode=true&characterEncoding=utf8
applicationContext.xml:
[java] view plain copy
< 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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" default-lazy-init="true" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<description>Spring公共配置文件 </description>
<!-- 定义受环境影响易变的变量 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE">
<property name="ignoreResourceNotFound" value="true">
<property name="locations">
<list>
<!-- 标准配置 -->
<value>classpath*:/application.properties</value>
<!-- 本地开发环境配置 -->
<value>classpath*:/application.local.properties</value>
<!-- 服务器生产环境配置 -->
<!---->file:/var/myapp/application.server.properties -->
<!--!----></list>
</property>
</property></property></bean>
<!-- 使用annotation 自动注册bean,并保证@Required,@Autowired的属性被注入 -->
<context:component-scan base-package="cn.puretext">
<!-- 数据源配置,使用应用内的DBCP数据库连接池 -->
<bean id="dataSourceContent" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="com.mysql.jdbc.Driver">
<property name="url" value="$jdbc.urlContent">
<property name="username" value="$jdbc.username">
<property name="password" value="$jdbc.password">
<!-- Connection Pooling Info -->
<property name="initialSize" value="5">
<property name="maxActive" value="100">
<property name="maxIdle" value="30">
<property name="maxWait" value="1000">
<property name="poolPreparedStatements" value="true">
<property name="defaultAutoCommit" value="false">
</property></property></property></property></property></property></property></property></property></property></bean>
<bean id="dataSourceIndex" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="com.mysql.jdbc.Driver">
<property name="url" value="$jdbc.urlIndex">
<property name="username" value="$jdbc.username">
<property name="password" value="$jdbc.password">
<!-- Connection Pooling Info -->
<property name="initialSize" value="5">
<property name="maxActive" value="100">
<property name="maxIdle" value="30">
<property name="maxWait" value="1000">
<property name="poolPreparedStatements" value="true">
<property name="defaultAutoCommit" value="false">
</property></property></property></property></property></property></property></property></property></property></bean>
<!-- 数据源配置,使用应用服务器的数据库连接池 -->
<!--<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/ExampleDB">-->
<!-- Hibernate配置 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSourceContent">
<property name="namingStrategy">
<bean class="org.hibernate.cfg.ImprovedNamingStrategy">
</bean></property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">$hibernate.show_sql</prop>
<prop key="hibernate.format_sql">$hibernate.format_sql</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider
</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">$hibernate.ehcache_config_file</prop>
</props>
</property>
<property name="packagesToScan" value="cn.puretext.entity.*">
</property></property></bean>
<!-- 事务管理器配置,单数据源事务 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory">
</property></bean>
<!-- 事务管理器配置,多数据源JTA事务-->
<!-- id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager or
WebLogicJtaTransactionManager">
-->
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager">
</tx:annotation-driven><!--!----><!--!--<jee:jndi-lookup--></context:component-scan></beans> 参考技术A Spring可以根据数据源不同建立两个事务管理器的把
Spring中实现多数据源事务管理
Spring中实现多数据源事务管理
前言
由于项目中引入了多个数据源,并且需要对多个数据源进行写操作,那么多数据源的事务管理自然成了不可避免的问题,这也让我对@Transactional
注解有了进一步的理解(但实际上也并不是非常深入)
然而这是一个演进的过程,刚开始项目中并没有使用@Transactional
指定具体的TransactionManager
,所以新增一个数据源后,对原有的事务产生了影响了,这也是偶尔在一次测试报错而结果没有回滚之后才发现的,遂对于@Transactional
注解的一些参数项进行了了解。
研究
由于容器中存在两个TransactionManager
,那么被@Transactional
注解的方法到底使用了哪个TransactionManager
来进行事务管理,抑或是同时使用了两个TransactionManager
来进行事务管理都是我们需要搞清楚的问题。
首先我们先看看@Transactional
注解上有没有提供配置项来指定TransactionManager
,果不其然,发现value
属性就是用来指定具体TransactionManager
的,通过id
或者name
来指定唯一一个TransactionManager
,那么对于只需要一个事务管理的方法,问题就简单多了:
@Transactional(value = "database2TransactionManager")
public void test(String a)
// business operation
关于不指定TransactionManager时会使用哪一个TransactionManager,有兴趣的童鞋可以参考另一篇文章,讲的比较清晰:http://blog.sina.com.cn/s/blog_8f61307b0100ynfb.html
好了,回到我们研究的问题,那么对于需要写入多个数据源的业务方法该怎么办呢?
进一步研究
看来@Transactional
是没有提供这种功能了,那么就自己写了一个吧。我记得Spring中的事务管理分编程式事务和声明式事务。我们平时使用的@Transactional
就是声明式事务,它的好处其实也就是灵活度更高、代码的耦合性更低,最终的事务管理实现还是一样的,只不过将具体逻辑都剥离到了切面中。所以我们可以手写一个切面来写一次“编程式事务”,当然在具体应用时,还是声明式的。
Java中一般编程式事务的写法:
public class UserServiceImpl implements UserService
@Resource
private TransactionManager txManager;
@Resource
private UserDao userDao;
@Resource
private AddressDao addressDao;
public boolean saveUser(User user)
TransactionDefinition txDefinition = new TransactionDefinition();
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try
result = userDao.save(user);
if(!result)
return false;
result = addressDao.save(user.getId(), user.getAddress());
txManager.commit(txStatus);
catch (Exception e)
result = false;
txManager.rollback(txStatus);
return result;
我们借用这个逻辑将事务管理相关提取到切面中,并在进入目标方法之前,让多个TransactionManager
都开启事务,并在成功执行后一并提交或失败后一并回滚,具体代码:
/**
* @author Zhu
* @date 2015-7-15
* @version 0.0.1
* @description
*/
public class MultiTransactionalAspect
private Logger logger = LoggerFactory.getLogger(getClass());
public Object around(ProceedingJoinPoint pjp,
MultiTransactional multiTransactional) throws Throwable
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<DataSourceTransactionManager>();
Stack<TransactionStatus> transactionStatuStack = new Stack<TransactionStatus>();
try
if (!openTransaction(dataSourceTransactionManagerStack,
transactionStatuStack, multiTransactional))
return null;
Object ret = pjp.proceed();
commit(dataSourceTransactionManagerStack, transactionStatuStack);
return ret;
catch (Throwable e)
rollback(dataSourceTransactionManagerStack, transactionStatuStack);
logger.error(String.format(
"MultiTransactionalAspect, method:%s-%s occors error:", pjp
.getTarget().getClass().getSimpleName(), pjp
.getSignature().getName()), e);
throw e;
/**
* @author Zhu
* @date 2015-7-25下午7:55:46
* @description
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
* @param values
*/
private boolean openTransaction(
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack,
MultiTransactional multiTransactional)
String[] transactionMangerNames = multiTransactional.values();
if (ArrayUtils.isEmpty(multiTransactional.values()))
return false;
for (String beanName : transactionMangerNames)
DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) ContextHolder
.getBean(beanName);
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(new DefaultTransactionDefinition());
transactionStatuStack.push(transactionStatus);
dataSourceTransactionManagerStack
.push(dataSourceTransactionManager);
return true;
/**
* @author Zhu
* @date 2015-7-25下午7:56:39
* @description
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void commit(
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack)
while (!dataSourceTransactionManagerStack.isEmpty())
dataSourceTransactionManagerStack.pop().commit(
transactionStatuStack.pop());
/**
* @author Zhu
* @date 2015-7-25下午7:56:42
* @description
* @param dataSourceTransactionManagerStack
* @param transactionStatuStack
*/
private void rollback(
Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
Stack<TransactionStatus> transactionStatuStack)
while (!dataSourceTransactionManagerStack.isEmpty())
dataSourceTransactionManagerStack.pop().rollback(
transactionStatuStack.pop());
整体结构很清晰:
1. 首先根据指定的多个TransactionManager
依次开启事务,这个次序不影响,因为其实大家都是平等的。
2. 其次就是调用目标方法执行具体的业务逻辑
3. 若是成功返回则提交每个事务,若中途报错,那么就回滚每个事务
其中为什么要用Stack
来保存TransactionManager
和TransactionStatus
呢?那是因为Spring的事务处理是按照LIFO/stack behavior的方式进行的。如若顺序有误,则会报错:
java.lang.IllegalStateException: Cannot deactivate transaction synchronization - not active
at org.springframework.transaction.support.TransactionSynchronizationManager.clearSynchronization(TransactionSynchronizationManager.java:313)
at org.springframework.transaction.support.TransactionSynchronizationManager.clear(TransactionSynchronizationManager.java:451)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:986)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:782)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactio
题外话
刚开始碰到这个问题的时候,先想到的是分布式事务管理,也去看了JTA相关的文章,但是好像比较麻烦,而且都是一些老文章,于是想试试自己实现,最后也实现了。所以想知道JTA TransactionManager
究竟有什么用呢?
以上是关于多数据源 spring怎么管理事务的的主要内容,如果未能解决你的问题,请参考以下文章