用过spring的人都熟悉spring的事务管理机制,并且spring支持jta分布式事务,那么,我一直在想,能否在代码中动态的切换事务类型呢?如果当前要操作的多个表在同一个数据源上,就直接本地
事务好了,如果当前操作的表分别在不同的数据源上,就切换成spring里配置好的jta事务。也就是说,事务还是用spring的声明式事务,但是用代码手动的选取用哪一个,能不能这样呢??
我做了个测试,在测试中,配置了两个spring配置文件,一个是使用本地事务,并且只有一个数据源,另一个是jta事务,并且有两个数据源,它们分别如下:
applicationContext-sys.xml(使用本地事务,并且只有一个数据源)
另一个:applicationContext-test-jotm.xml(jta事务,并且有两个数据源)
再附上关键的service:
ArticleService.java:
ArticleServiceImpl.java:
再附上测试代码:
不知道有谁能提供更好的测试方案,如果有在代码中切换spring里的事务的方案更好
事务好了,如果当前操作的表分别在不同的数据源上,就切换成spring里配置好的jta事务。也就是说,事务还是用spring的声明式事务,但是用代码手动的选取用哪一个,能不能这样呢??
我做了个测试,在测试中,配置了两个spring配置文件,一个是使用本地事务,并且只有一个数据源,另一个是jta事务,并且有两个数据源,它们分别如下:
applicationContext-sys.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: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-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:conf/jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${dialect}
</prop>
<prop key="hibernate.default_batch_fetch_size">10</prop>
<prop key="hibernate.max_fetch_depth">0</prop>
<prop key="hibernate.show_sql">${show_sql}</prop>
</props>
</property>
<property name="mappingDirectoryLocations">
<list>
<value>classpath*:/com/zhangweilin/po/</value>
</list>
</property>
</bean>
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
<property name="nestedTransactionAllowed" value="true" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<aop:config>
<aop:pointcut id="commonServiceOperation"
expression="execution(* com.zhangweilin.service.*Service.*(..))" />
<aop:advisor pointcut-ref="commonServiceOperation"
advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="init*" rollback-for="Exception" />
<tx:method name="save*" rollback-for="Exception" />
<tx:method name="create*" rollback-for="Exception" />
<tx:method name="login*" rollback-for="Exception" />
<tx:method name="add*" rollback-for="Exception" />
<tx:method name="update*" rollback-for="Exception" />
<tx:method name="modify*" rollback-for="Exception" />
<tx:method name="delete*" rollback-for="Exception" />
<tx:method name="remove*" rollback-for="Exception" />
<tx:method name="clear*" rollback-for="Exception" />
<tx:method name="register*" rollback-for="Exception" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<!--
<bean id="ehcacheManager"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="/WEB-INF/classes/ehcache.xml" />
</bean>
-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="dao" class="com.zhangweilin.core.DaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
</beans>
另一个:applicationContext-test-jotm.xml(jta事务,并且有两个数据源)
<?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: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-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- jotm配置 -->
<bean id="mysqldatasource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource">
<property name="dataSource">
<!--内部XA数据源 -->
<bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
<property name="transactionManager" ref="jotm" />
<property name="driverName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://172.16.18.75:3306/?useUnicode=true&characterEncoding=UTF-8" />
</bean>
</property>
<property name="user" value="root" />
<property name="password" value="123456" />
</bean>
<bean id="h2datasource" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource">
<property name="dataSource">
<!--内部XA数据源 -->
<bean class="org.enhydra.jdbc.standard.StandardXADataSource" destroy-method="shutdown">
<property name="transactionManager" ref="jotm" />
<property name="driverName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/pay?useUnicode=true&characterEncoding=UTF-8" />
</bean>
</property>
<property name="user" value="zwllxs" />
<property name="password" value="123456" />
</bean>
<bean id="sessionFactory1" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="mysqldatasource"/>
<property name="mappingDirectoryLocations">
<list>
<value>classpath*:/com/zhangweilin/po/</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.jdbc.batch_size">50</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
</props>
</property>
<!--
<property name="jtaTransactionManager">
<ref bean="jotm" />
</property>
-->
</bean>
<bean id="sessionFactory2" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="h2datasource"/>
<property name="mappingDirectoryLocations">
<list>
<value>classpath*:/com/zhangweilin/po/</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.jdbc.batch_size">50</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
</props>
</property>
<!--
<property name="jtaTransactionManager">
<ref bean="jotm" />
</property>
-->
</bean>
<bean id="dao1" class="com.zhangweilin.core.DaoImpl">
<property name="sessionFactory" ref="sessionFactory1" />
</bean>
<bean id="dao2" class="com.zhangweilin.core.DaoImpl">
<property name="sessionFactory" ref="sessionFactory2" />
</bean>
<bean id="loginLogService2" class="com.zhangweilin.service.impl.LoginLogServiceImpl">
<property name="dao" ref="dao1" />
</bean>
<bean id="articleService2" class="com.zhangweilin.service.impl.ArticleServiceImpl">
<property name="dao" ref="dao2" />
<property name="loginLogService" ref="loginLogService2" />
</bean>
<bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean" />
<!-- jtatransactionmanager容器 -->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="userTransaction" ref="jotm" />
</bean>
<tx:advice id="txAdvice2" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="upd*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="interceptorPointCuts" expression="execution(* com.zhangweilin.service.*Service.*(..))" />
<aop:advisor id="jotmAdvisor" advice-ref="txAdvice2" pointcut-ref="interceptorPointCuts" />
</aop:config>
</beans>
再附上关键的service:
ArticleService.java:
/**
* 文章
* @author zhangweilin
*
*/
public interface ArticleService extends Service<Article>
{
/**
* 测试同时添加分布在不同库上的Article和LoginLog;
* @param article
* @throws Exception
*/
public void addArticle(Article article,String name) throws Exception;
public void setName(String name);
public String getName();
}
ArticleServiceImpl.java:
/**
* 文章
* @author zhangweilin
*
*/
public class ArticleServiceImpl extends ServiceImpl<Article> implements ArticleService
{
private LoginLogService loginLogService;
private String name="张伟林";
/**
* @param loginLogService the loginLogService to set
*/
public void setLoginLogService(LoginLogService loginLogService)
{
System.out.println("setLoginLogService: "+loginLogService);
this.loginLogService = loginLogService;
}
@Override
public void addArticle(Article article,String name) throws Exception
{
save(article);
LoginLog loginLog=new LoginLog();
loginLog.setAddress(name);
loginLog.setAdmin(null);
loginLog.setIp("11.11.11.11");
// loginLog.setLoginTime(new Date());
System.out.println(" 添加");
loginLogService.save(loginLog);
System.out.println("操作结束");
}
@Override
public String getName()
{
System.out.println("getName: "+name);
return this.name;
}
@Override
public void setName(String name)
{
System.out.println("setName: "+name);
this.name=name;
}
再附上测试代码:
package com.zhangweilin.test;
import java.util.Date;
import org.aopalliance.aop.Advice;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import com.zhangweilin.po.Article;
import com.zhangweilin.service.ArticleService;
import com.zhangweilin.service.LoginLogService;
/**
* 测试在代码中动态切换spring的事务策略
* @author wlzhang
*
*/
public class TestServiceJotm
{
private static BeanFactory fac;
private static LoginLogService loginLogService2;
private static ArticleService articleService;
private static HibernateTransactionManager hibernateTransactionManager;
private static PlatformTransactionManager platformTransactionManager;
private static AbstractPlatformTransactionManager abstractPlatformTransactionManager;
/**
* 初始化环境
*/
@BeforeClass
public static void init()
{
try
{
fac = new ClassPathXmlApplicationContext("conf\\spring\\applicationContext*.xml");
loginLogService2 = (LoginLogService) fac.getBean("loginLogService2");
articleService = (ArticleService) fac.getBean("articleService2");
}
catch (Throwable t)
{
t.printStackTrace();
}
}
/**
* 尝试切换事务管理器
*/
@Test
public void testChangeTransactionManager()
{
//先测试单例效应
System.out.println("articleService.name: "+articleService.getName());//第一次获取name值
articleService.setName("伟林张");
articleService = (ArticleService) fac.getBean("articleService2");
System.out.println("articleService.name2: "+articleService.getName());//第二次获取name值,如果spring配置采用的单例,那么此获取的就是修改后的name值
//尝试着切换事务管理器
Object jotm=fac.getBean("jotm");
System.out.println("jotm: "+jotm+" , "+jotm.getClass());
Object interceptorPointCuts=fac.getBean("interceptorPointCuts");
System.out.println("interceptorPointCuts: "+interceptorPointCuts+" , "+interceptorPointCuts.getClass());
DefaultBeanFactoryPointcutAdvisor jotmAdvisor=(DefaultBeanFactoryPointcutAdvisor) fac.getBean("jotmAdvisor");
System.out.println("jotmAdvisor: "+jotmAdvisor+" , "+jotmAdvisor.getClass());
hibernateTransactionManager = (HibernateTransactionManager) fac.getBean("txManager");
System.out.println("hibernateTransactionManager: "+hibernateTransactionManager);
platformTransactionManager = (PlatformTransactionManager) fac.getBean("transactionManager");
System.out.println("platformTransactionManager: "+platformTransactionManager);
System.out.println("切换前");
if (platformTransactionManager instanceof JtaTransactionManager)
{
System.out.println("当前事务: JtaTransactionManager");
JtaTransactionManager jtaTransactionManager=(JtaTransactionManager) platformTransactionManager;
System.out.println("转成jtaTransactionManager: "+jtaTransactionManager);
System.out.println("准备切换");
platformTransactionManager=hibernateTransactionManager; //尝试将hibernate事务管理器赋给jta事务管理器
System.out.println("尝试切换成功:platformTransactionManager: "+platformTransactionManager);
}
else if (platformTransactionManager instanceof HibernateTransactionManager)
{
System.out.println("当前事务: HibernateTransactionManager");
HibernateTransactionManager jtaTransactionManager=(HibernateTransactionManager) platformTransactionManager;
System.out.println("转成jtaTransactionManager: "+jtaTransactionManager);
}
System.out.println("切换后");
platformTransactionManager = (PlatformTransactionManager) fac.getBean("transactionManager");
System.out.println("platformTransactionManager: "+platformTransactionManager);
if (platformTransactionManager instanceof JtaTransactionManager)
{
//在“切换”后,还是调用的这里。说明切换不成功
System.out.println("当前事务: JtaTransactionManager");
JtaTransactionManager jtaTransactionManager=(JtaTransactionManager) platformTransactionManager;
System.out.println("转成jtaTransactionManager: "+jtaTransactionManager);
System.out.println("准备切换");
platformTransactionManager=hibernateTransactionManager;
System.out.println("尝试切换成功");
}
else if (platformTransactionManager instanceof HibernateTransactionManager)
{
System.out.println("当前事务: HibernateTransactionManager");
HibernateTransactionManager hibernateTransactionManager=(HibernateTransactionManager) platformTransactionManager;
System.out.println("转成HibernateTransactionManager: "+hibernateTransactionManager);
}
//结果,切换引用是行不通的,而更改现有的单例对象的值是可以的
}
/**
* 切换事务管理器失败,尝试切换 Advisor
*/
@Test
public void testChangeAdvisor()
{
DefaultBeanFactoryPointcutAdvisor jotmAdvisor=(DefaultBeanFactoryPointcutAdvisor) fac.getBean("jotmAdvisor");
System.out.println("jotmAdvisor: "+jotmAdvisor+" , "+jotmAdvisor.getClass());
System.out.println("jotmAdvisor: "+jotmAdvisor.getAdviceBeanName());
jotmAdvisor.setAdviceBeanName("txAdvice");
jotmAdvisor=(DefaultBeanFactoryPointcutAdvisor) fac.getBean("jotmAdvisor");
System.out.println("jotmAdvisor: "+jotmAdvisor.getAdviceBeanName());//adviceBeanName名被更改了,。幻想着切换事务的可行性增大了
// Advice advice=jotmAdvisor.getAdvice();
}
/**
* 先不做切换的数据操作,完全符合自己预期
* @throws Exception
*/
@Test
public void testArticleService() throws Exception
{
System.out.println("articleService: "+articleService);
Article article=new Article();
article.setAddTime(new Date());
article.setContent("太好了");
article.setTitle("jotm测试下");
article.setType(20);
articleService.addArticle(article,"上海");
}
/**
* Advisor看起来貌似切换成功了,所以正式尝试切换事务来增加数据,测试中保证让数据库端一个表不能正常插入数据,以测试事务的一致性
*/
@Test
public void testChangeTrancation()
{
DefaultBeanFactoryPointcutAdvisor jotmAdvisor=(DefaultBeanFactoryPointcutAdvisor) fac.getBean("jotmAdvisor");
System.out.println("使用默认事务:jta事务");
Article article=new Article();
article.setAddTime(new Date());
article.setContent("使用默认事务:jta事务太好了");
article.setTitle("使用默认事务:jta事务_jotm测试下");
article.setType(20);
try
{
articleService.addArticle(article,"使用默认事务:jta事务_上海");
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
jotmAdvisor.setAdviceBeanName("txAdvice2"); //尝试以更改adviceBeanName的方式修改指定的事务
jotmAdvisor=(DefaultBeanFactoryPointcutAdvisor) fac.getBean("jotmAdvisor");
System.out.println("事务已切换,使用本地事务");
article=new Article();
article.setAddTime(new Date());
article.setContent("事务已切换,使用本地事务_事务太好了");
article.setTitle("事务已切换,使用本地事务_jotm测试下");
article.setType(20);
try
{
articleService.addArticle(article,"事务已切换,使用本地事务_上海");
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
/**
* 切换事务失败,事务的一致性并没有符合自己的预期。经过查询文档,关于setAdviceBeanName方法的描述如下,
* Specify the name of the advice bean that this advisor should refer to.
An instance of the specified bean will be obtained on first access of this advisor's advice. This advisor will only ever obtain at most one single instance of
the advice bean, caching the instance for the lifetime of the advisor.
* 只在第一次访问时才创建,之后不能更改,哎,杯具
*/
}
不知道有谁能提供更好的测试方案,如果有在代码中切换spring里的事务的方案更好