Spring 中集成 JOTM 配置 JTA 事务:
假如业务中要用到多个数据库,我们希望在业务方法中,当对某一个数据库的数据表进行操作的事务失败并回退(rollback),另外某一个数据库的数据表的操作事务也要回退,但应用一般的事务管理达不到这样的事务管理效果,这就需要实现 JTA 事务管理了。
这里我们在SPring中集成 Object web 的一个开源JTA实现JOTM (可以在http://jotm.objectweb.org下载完整版) 来实现JTA事务管理。
1、将必须的类包放入类路径中:
jotm.jar, xapool.jar, jotm_jrmp_stubs.jar, jta-spect1_0_1.jar, connector-1_5.jar等等。
2、编写JOTM配置文件carol.properties,将其放到类路径下:
#JNDI调用协议 carol.protocols=jrmp #不使用CAROL JNDI封装器 carol.start.jndi=false #不启动命名服务器 carol.start.ns=false
3、在MYSQL中创建两个数据库 "jtatesta","jtatestb":
CREATE DATABASE IF NOT EXISTS jtatesta; USE jtatesta; DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `user_id` int(10) unsigned NOT NULL auto_increment, `user_name` varchar(45) NOT NULL, `user_password` varchar(45) NOT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1; INSERT INTO `user` (`user_id`,`user_name`,`user_password`) VALUES (1,'tufu','tufu'); CREATE DATABASE IF NOT EXISTS jtatestb; USE jtatestb; DROP TABLE IF EXISTS `grade`; CREATE TABLE `grade` ( `grade_id` int(10) unsigned NOT NULL auto_increment, `user_id` int(10) unsigned NOT NULL, `grade` double NOT NULL, PRIMARY KEY (`grade_id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1; INSERT INTO `grade` (`grade_id`,`user_id`,`grade`) VALUES (1,0,100);
4、域对象、数据访问类和其他事务管理的一样,如:
//Domain对象User.java: package com.domain; import java.io.Serializable; public class User implements Serializable { private int user_id; private String user_name; private String user_password; ......//省略set、get方法 } //Domain对象Grade.java: package com.domain; import java.io.Serializable; public class Grade implements Serializable{ private int grade_id; private User user; private double grade; .....//省略set、get方法 } 应用Spring JDBC的DAO:(省略DAO接口) //UserJdbcDao.java: package com.dao.jdbc; import org.springframework.jdbc.core.support.JdbcDaoSupport; import com.dao.UserDao; import com.domain.User; public class UserJdbcDao extends JdbcDaoSupport implements UserDao{ public void addUser(User user){ String SQL = "INSERT INTO user(user_id,user_name,user_password) VALUES(?,?,?)"; Object[] params = new Object[]{ user.getUser_id(),user.getUser_name(),user.getUser_password() }; this.getJdbcTemplate().update(SQL, params); } } //GradeJdbcDao.java: package com.dao.jdbc; import com.dao.GradeDao; import com.domain.Grade; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class GradeJdbcDao extends JdbcDaoSupport implements GradeDao{ public void addGrade(Grade grade){ final String SQL = "INSERT INTO grade(user_id,grade) VALUES(?,?)"; Object[] params = new Object[]{ grade.getUser().getUser_id(),grade.getGrade() }; this.getJdbcTemplate().update(SQL, params); } }
5、应用了JTA事务管理的业务类(省略了接口),用@Transactional注解标注,以在配置文件中可以用<tx:annotation-driven>注解驱动自动进行事务增强:
package com.service.impl; import com.dao.GradeDao; import com.dao.UserDao; import com.domain.*; import org.springframework.transaction.annotation.Transactional; import com.service.MyService; @Transactional public class MyServiceImpl implements MyService { private UserDao userDao; private GradeDao gradeDao; public void setUserDao(UserDao userDao){ this.userDao = userDao; } public void setGradeDao(GradeDao gradeDao){ this.gradeDao = gradeDao; } @Transactional(readOnly=false) public void addGrade(User user,Grade grade){ //假如希望两个添加数据的事务,其中有一个添加失败时,均回滚, //由于两个操作是在两个不同的数据库上进行的,故要JTA事务来进行管理 //否则,将会出现添加一个,回滚一个的情形 gradeDao.addGrade(grade); userDao.addUser(user); } }
6、spring为JOTM提供了一个org.springframework.transaction.jta.JotmFactoryBean 支持类,可以用其方便地创建本地JOTM实例。
具体的配置文件app_jta.xml如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsp="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsp:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"> <!--JOTM本地实例--> <bean id="jotm" class="org.springframework.transaction.jta.JotmFactoryBean"/> <!--JTA事务管理器--> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="userTransaction" ref="jotm"/><!--指定userTransaction属性引用JOTM本地实例--> </bean> <!--XAPool配置,内部包含了一XA数据源,对应了数据库jtatesta 支持JTA事务的数据源,必须封装成XAPool--> <bean id="jtaTestADS" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <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/jtatesta"/> </bean> </property> <property name="user" value="root"/> <property name="password" value="885123"/> </bean> <!--类似地,对应了数据库jtatestb的XAPool配置,内部包含了一XA数据源--> <bean id="jtaTestBDS" class="org.enhydra.jdbc.pool.StandardXAPoolDataSource" destroy-method="shutdown"> <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/jtatestb"/> </bean> </property> <property name="user" value="root"/> <property name="password" value="885123"/> </bean> <!--分别配置访问jtaTestADS、jtaTestBDS数据源的Spring JDBC模板--> <bean id="jtaTestATemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="jtaTestADS"/> </bean> <bean id="jtaTestBTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="jtaTestBDS"/> </bean> <!--分别配置基于模板jtaTestADS,jtaTestBDS的DAO--> <bean id="userDao" class="com.dao.jdbc.UserJdbcDao"> <property name="jdbcTemplate" ref="jtaTestATemplate"/> </bean> <bean id="gradeDao" class="com.dao.jdbc.GradeJdbcDao"> <property name="jdbcTemplate" ref="jtaTestBTemplate"/> </bean> <!--跨数据库的JTA事务的业务类--> <bean id="myService" class="com.service.impl.MyServiceImpl"> <property name="userDao" ref="userDao"/> <property name="gradeDao" ref="gradeDao"/> </bean> <!--注解事务驱动--> <tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> </beans>
7、测试main方法:
import com.service.MyService; import com.service.impl.MyServiceImpl; import com.domain.*; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestMain { public static void main(String args[]){ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans_jta.xml"); MyService ms = (MyServiceImpl)ctx.getBean("myService"); User user = new User(); //特意添加一个重复的主键,以使添加user的事务失败并回退 //如果此时应用JTA事务失败,将仍会执行添加grade的事务并提交(前提是先于添加user操作) //如果应用JTA事务成功,就会两个添加事务同时执行或同时回退。 user.setUser_id(1); user.setUser_name("tufu"); user.setUser_password("tufu"); Grade grade = new Grade(); grade.setGrade(100); grade.setUser(user); ms.addGrade(user,grade); } }
注:将log4j.properties中的log4j日志设置为DEBUG级别,可以看到详细的JTA事务执行情况:
.......
log4j.rootLogger=DEBUG,R,A1
.......