分布式事务JTA之实践:Spring+ATOMIKOS
作者: Michael 日期: 2012 年 2 月 28 日
本文的目录结构如下:
- 一、概述
- 二、应用场景
- 三、实验模拟需求
- 四、实例测试环境
- 五、源代码下载及配置介绍
- 六、测试验证
一、概述:
本文主要讲述如何基于Atomikos 和spring在项目中实现分布式事务管理
二、应用场景:
如果项目中的数据源来自多个数据库,同时又需要在多数据源中保证事务,此时就需要用到分布式事务处理了。
三、实验模拟需求:
比如有两个对象:用户信息、用户存款,用户信息存在数据库A、存款信息存在数据库B,若客户甲向乙转账,需要在数据库B中对甲、乙的存款信息修改,同时在数据库A中把甲、乙的备注信息最新为最近一次的操作时间。
四、实例测试环境:
- spring、hibernate3.2
- mysql5.1.51(需要版本5.0+)
-
AtomikosTransactionsEssentials-3.7.0 (详细可参加它的官网:http://www.atomikos.com )
说明:
1. 测试的数据库需要支持分布式事务,同时JDBC要支持XA连接驱动。本次测试用的mysql5.1是支持事务的,JDBC驱动版本:mysql-connector-java-5.1.7-bin.jar,包含对 XA连接的支持:com.mysql.jdbc.jdbc2.optional.MysqlXAConnection。
2. 附件提供AtomikosTransactionsEssentials 3.7.0 lib包下载:AtomikosTransactionsEssentials-3.7.0-lib.zip。官方下载地址:http://www.atomikos.com/Main/TransactionsEssentialsDownloadForm,需要先注册才能下载。同时这里也提供目前3.7.0的下载链接:http://www.atomikos.com/downloads/transactions-essentials/com/atomikos/AtomikosTransactionsEssentials/3.7.0/AtomikosTransactionsEssentials-3.7.0-bin.zip
五、代码及配置介绍:
源代码下载:分布式事务实例演示源代码michael_jta_code.zip
1.代码的目录结构图如下:
转账测试的的代码片段:
8 |
public boolean doTestTransfer(String srcId, String destId, float money) {
|
10 |
BankAccount srcAccount = bankAccountDao.getByUserName(srcId);
|
11 |
BankAccount destAccount = bankAccountDao.getByUserName(destId);
|
12 |
if (srcAccount.getDeposit() < money) {
|
13 |
System.out.println( "warn :" + srcAccount.getUserName()
|
14 |
+ " has not enough money to transfer" );
|
17 |
srcAccount.setDeposit(srcAccount.getDeposit() - money);
|
18 |
destAccount.setDeposit(destAccount.getDeposit() + money);
|
20 |
bankAccountDao.update(srcAccount);
|
21 |
bankAccountDao.update(destAccount);
|
23 |
Date curTime = new Date();
|
24 |
UserInfo srcUser = userInfoDao.getById(srcId);
|
25 |
UserInfo destUser = userInfoDao.getById(destId);
|
27 |
destUser.setRemark1(curTime + "" );
|
28 |
destUser.setRemark2(curTime + "" );
|
30 |
userInfoDao.update(destUser);
|
31 |
srcUser.setRemark1(curTime + "" );
|
32 |
if (srcAccount.getDeposit() < 18000 ) {
|
33 |
throw new RuntimeException( "michael test exception for JTA " );
|
35 |
srcUser.setRemark2(curTime + "" );
|
37 |
userInfoDao.update(srcUser);
|
38 |
System.out.println( "success done:" + srcAccount.getUserName()
|
39 |
+ " transfer ¥" + money + " to " + destAccount.getUserName());
|
2. 配置文件详细介绍:
jta.jdbc.properties
3 |
jdbc.SDS.class=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource |
6 |
jdbc.SDS2.class=com.mysql.jdbc.jdbc2.optional.MysqlXADataSource |
jta.properties
2 |
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory |
3 |
com.atomikos.icatch.console_file_name = tm.out |
4 |
com.atomikos.icatch.log_base_name = tmlog |
5 |
com.atomikos.icatch.tm_unique_name = com.atomikos.spring.jdbc.tm |
6 |
com.atomikos.icatch.console_log_level = INFO |
jta1.hibernate.cfg.xml
1 |
<!DOCTYPE hibernate-configuration |
2 |
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
|
5 |
< hibernate-configuration >
|
7 |
< property name = "dialect" >
|
8 |
org.hibernate.dialect.MySQL5Dialect
|
10 |
< property name = "hbm2ddl.auto" >update</ property >
|
11 |
< mapping class = "michael.jta.atomikos.domain.UserInfo" />
|
14 |
</ hibernate-configuration >
|
jta2.hibernate.cfg.xml
1 |
<!DOCTYPE hibernate-configuration |
2 |
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
|
5 |
< hibernate-configuration >
|
8 |
< property name = "show_sql" >true</ property >
|
9 |
< property name = "dialect" >
|
10 |
org.hibernate.dialect.MySQL5Dialect
|
12 |
< property name = "hbm2ddl.auto" >update</ property >
|
13 |
< mapping class = "michael.jta.atomikos.domain.BankAccount" />
|
16 |
</ hibernate-configuration >
|
jta.spring.xml
1 |
<? xml version = "1.0" encoding = "UTF-8" ?>
|
5 |
< bean id = "propertyConfigurer"
|
6 |
class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
|
7 |
< property name = "locations" >
|
9 |
< value >classpath:jta.jdbc.properties</ value >
|
15 |
< bean id = "SDS" class = "com.atomikos.jdbc.SimpleDataSourceBean"
|
16 |
init-method = "init" destroy-method = "close" >
|
17 |
< property name = "uniqueResourceName" >
|
20 |
< property name = "xaDataSourceClassName" >
|
21 |
< value >${jdbc.SDS.class}</ value >
|
23 |
< property name = "xaDataSourceProperties" >
|
24 |
< value >${jdbc.SDS.properties}</ value >
|
26 |
< property name = "exclusiveConnectionMode" >
|
29 |
< property name = "connectionPoolSize" >
|
32 |
< property name = "validatingQuery" >
|
33 |
< value >SELECT 1</ value >
|
37 |
< bean id = "SDS2" class = "com.atomikos.jdbc.SimpleDataSourceBean"
|
38 |
init-method = "init" destroy-method = "close" >
|
39 |
< property name = "uniqueResourceName" >
|
42 |
< property name = "xaDataSourceClassName" >
|
43 |
< value >${jdbc.SDS2.class}</ value >
|
45 |
< property name = "xaDataSourceProperties" >
|
46 |
< value >${jdbc.SDS2.properties}</ value >
|
48 |
< property name = "exclusiveConnectionMode" >
|
51 |
< property name = "connectionPoolSize" >
|
54 |
< property name = "validatingQuery" >
|
55 |
< value >SELECT 1</ value >
|
60 |
< bean id = "sessionFactory1"
|
61 |
class = "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
|
62 |
< property name = "dataSource" ref = "SDS" />
|
63 |
< property name = "configLocation"
|
64 |
value = "classpath:jta1.hibernate.cfg.xml" />
|
67 |
< bean id = "sessionFactory2"
|
68 |
class = "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
|
69 |
< property name = "dataSource" ref = "SDS2" />
|
70 |
< property name = "configLocation"
|
71 |
value = "classpath:jta2.hibernate.cfg.xml" />
|
76 |
< bean id = "atomikosTransactionManager"
|
77 |
class = "com.atomikos.icatch.jta.UserTransactionManager"
|
78 |
init-method = "init" destroy-method = "close" >
|
80 |
< property name = "forceShutdown" >
|
86 |
< bean id = "atomikosUserTransaction"
|
87 |
class = "com.atomikos.icatch.jta.UserTransactionImp" >
|
88 |
< property name = "transactionTimeout" >
|
94 |
< bean id = "springJTATransactionManager"
|
95 |
class = "org.springframework.transaction.jta.JtaTransactionManager" >
|
97 |
< property name = "transactionManager" >
|
98 |
< ref bean = "atomikosTransactionManager" />
|
100 |
< property name = "userTransaction" >
|
101 |
< ref bean = "atomikosUserTransaction" />
|
106 |
< bean id = "userInfoDao"
|
107 |
class = "michael.jta.atomikos.dao.impl.UserInfoDaoImpl" >
|
108 |
< property name = "sessionFactory" ref = "sessionFactory1" />
|
110 |
< bean id = "bankAccountDao"
|
111 |
class = "michael.jta.atomikos.dao.BankAccountDao" >
|
112 |
< property name = "sessionFactory" ref = "sessionFactory2" />
|
115 |
< bean id = "bankAccountService"
|
116 |
class = "michael.jta.atomikos.service.impl.BankAccountServiceImpl" >
|
117 |
< property name = "userInfoDao" ref = "userInfoDao" />
|
118 |
< property name = "bankAccountDao" ref = "bankAccountDao" />
|
122 |
< bean id = "transactionInterceptor"
|
123 |
class = "org.springframework.transaction.interceptor.TransactionInterceptor" >
|
124 |
< property name = "transactionManager"
|
125 |
ref = "springJTATransactionManager" />
|
126 |
< property name = "transactionAttributes" >
|
128 |
< prop key = "*" >PROPAGATION_REQUIRED</ prop >
|
134 |
< bean id = "baseTransactionProxy"
|
135 |
class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
|
137 |
< property name = "transactionManager"
|
138 |
ref = "springJTATransactionManager" />
|
139 |
< property name = "transactionAttributes" >
|
142 |
< prop key = "*" >PROPAGATION_REQUIRED</ prop >
|
147 |
< bean id = "bankAccountServiceProxy" parent = "baseTransactionProxy" >
|
148 |
< property name = "target" >
|
149 |
< ref bean = "bankAccountService" />
|
六、测试验证
1. 初始化数据:
因为mysql数据库表的类型有事务和非事务之分,建表时一定要注意确保表的类型是事务控制的:InnoDB
数据库A(192.168.8.253):
1 |
DROP DATABASE IF EXISTS demota;
|
2 |
CREATE DATABASE demota;
|
4 |
DROP TABLE IF EXISTS tb_user_info;
|
6 |
CREATE TABLE tb_user_info (
|
13 |
INSERT INTO tb_user_info (user_name,real_name,remark1,remark2) VALUES ( 'husband' , 'husband' , '' , '' );
|
14 |
INSERT INTO tb_user_info (user_name,real_name,remark1,remark2) VALUES ( 'wife' , 'wife' , '' , '' );
|
数据库B(192.168.8.150):
1 |
DROP DATABASE IF EXISTS demota;
|
2 |
CREATE DATABASE demota;
|
4 |
DROP TABLE IF EXISTS tb_account;
|
5 |
CREATE TABLE tb_account (
|
12 |
INSERT INTO tb_account (user_name,deposit) VALUES ( 'husband' ,20000.00);
|
13 |
INSERT INTO tb_account (user_name,deposit) VALUES ( 'wife' ,10000.00);
|
2. 测试过程:
ps: 代码中模拟了异常出现的条件:如果账户金额<18000会抛出异常
JtaRunMainTest.java
1 |
package michael.jta.atomikos;
|
3 |
import michael.jta.atomikos.service.BankAccountService;
|
5 |
import org.springframework.context.ApplicationContext;
|
6 |
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
12 |
public class JtaRunMainTest {
|
17 |
public static void main(String[] args) {
|
18 |
System.out.println( "------------start" );
|
19 |
ApplicationContext appCt = new ClassPathXmlApplicationContext(
|
21 |
System.out.println( "------------finished init xml" );
|
23 |
Object bean = appCt.getBean( "bankAccountServiceProxy" );
|
24 |
System.out.println(bean.getClass());
|
25 |
BankAccountService service = (BankAccountService) bean;
|
26 |
service.doTestTransfer( "husband" , "wife" , 2000 );
|
运行第一次结果:
2 |
------------finished init xml |
4 |
Hibernate: select bankaccoun0_. id as id2_, bankaccoun0_.deposit as deposit2_, bankaccoun0_.user_name as user3_2_ from tb_account bankaccoun0_ where bankaccoun0_.user_name=?
|
5 |
Hibernate: select bankaccoun0_. id as id2_, bankaccoun0_.deposit as deposit2_, bankaccoun0_.user_name as user3_2_ from tb_account bankaccoun0_ where bankaccoun0_.user_name=?
|
6 |
success done :husband transfer ¥2000.0 to wife
|
7 |
Hibernate: update tb_account set deposit=?, user_name=? where id =?
|
8 |
Hibernate: update tb_account set deposit=?, user_name=? where id =?
|
运行第二次结果:
2 |
------------finished init xml |
4 |
Hibernate: select bankaccoun0_. id as id2_, bankaccoun0_.deposit as deposit2_, bankaccoun0_.user_name as user3_2_ from tb_account bankaccoun0_ where bankaccoun0_.user_name=?
|
5 |
Hibernate: select bankaccoun0_. id as id2_, bankaccoun0_.deposit as deposit2_, bankaccoun0_.user_name as user3_2_ from tb_account bankaccoun0_ where bankaccoun0_.user_name=?
|
6 |
Exception in thread "main" java.lang.RuntimeException: michael test exception for JTA
|
7 |
at michael.jta.atomikos.service.impl.BankAccountServiceImpl.doTestTransfer(BankAccountServiceImpl.java:51) |
8 |
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
9 |
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
|
10 |
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
|
11 |
at java.lang.reflect.Method.invoke(Method.java:597)
|
12 |
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:299)
|
13 |
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:172)
|
14 |
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:139)
|
15 |
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:107)
|
16 |
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
|
17 |
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
|
18 |
at $Proxy11.doTestTransfer(Unknown Source)
|
19 |
at michael.jta.atomikos.JtaRunMainTest.main(JtaRunMainTest.java:26)
|
测试过程中数据库查询的结果截图:
从上面的数据库截图可见,第一正常运行时两个数据库同步更新了,第二次运行发生异常后,两个数据库的数据为发生变化,实现了事务回滚。
分享到:
相关推荐
本篇文章将探讨如何在Java环境中,利用Spring框架和Atomikos这样的第三方工具实现分布式事务管理,即JTA(Java Transaction API)的实践。 JTA是Java平台提供的一种标准API,用于管理跨越多个资源(如数据库、消息...
Atomikos 是一个开源的JTA实现,它为Spring提供了一个轻量级、高性能的分布式事务解决方案。本文将深入探讨如何使用Spring、JTA和Atomikos来实现分布式事务。 首先,了解JTA的基本概念。JTA定义了一套规范,允许在...
Atomikos是著名的JTA(Java Transaction API)实现之一,提供了分布式事务管理的能力。AtomikosDataSource是基于Atomikos实现的XAPool,可以协调跨多个数据源的ACID(原子性、一致性、隔离性和持久性)事务。在...
本文将深入探讨如何使用Spring、MyBatis和Atomikos实现JTA分布式事务。 首先,让我们理解JTA的概念。Java Transaction API(JTA)是Java EE规范之一,用于处理分布式环境中的事务。它允许应用程序在不同的资源管理...
本教程将探讨如何利用Spring Boot、Druid、Mybatis以及Atomikos来配置多数据源并实现分布式事务。 首先,Spring Boot是Java生态系统中的一个流行框架,它简化了设置和配置过程,使得开发人员可以快速启动新项目。在...
总的来说,"spring+atomikos+druid分布式事务Demo"是一个学习和实践分布式事务处理的实用案例。通过理解和运行这个示例,开发者能够掌握如何在Spring环境中使用Atomikos和Druid实现高可用和一致性的分布式系统。
- **Atomikos、Bitronix等JTA实现**:这些第三方库实现了JTA规范,可以用来管理分布式事务。 6. **配置Spring JTA事务**:在Spring的配置文件中,你需要配置 `UserTransaction` 和 `TransactionManager`,并启用...
本案例主要探讨如何利用Spring Boot、Atomikos、JTA(Java Transaction API)、Hibernate以及MyBatis,结合MySQL数据库,实现一个跨数据源的分布式事务解决方案。 首先,Spring Boot是一个基于Spring框架的快速开发...
jta Springboot + Atomikos + Druid + Mysql 实现JTA分布式事务 问题一:Druid 和 Mysql 版本的问题 问题二:Springtest 默认情况下事务回滚 Mysql对XA协议支持不好,有待看
Spring框架提供了强大的支持来处理分布式事务,而Atomikos是一个开源的事务管理器,专门用于处理JTA(Java Transaction API)事务,尤其适用于微服务和分布式环境。本教程将探讨如何结合Spring和Atomikos来实现...
本项目“springboot+jta+atomikos”旨在利用Spring Boot框架、Java Transaction API (JTA) 和 Atomikos 这一开源事务管理器来实现分布式事务的统一管理。下面将详细阐述这三个关键组件以及它们如何协同工作。 **...
Spring Boot作为轻量级的Java开发框架,结合Atomikos这样的分布式事务管理器,可以有效地解决这些问题。本文将深入探讨如何在Spring Boot项目中实现Atomikos分布式事务以及动态数据源切换的两种示例。 首先,我们...
在分布式系统中,确保数据的一致性和完整性是至关重要的,这就是分布式事务的职责所在。...对于理解和实践分布式事务,以及深入研究Spring Boot、MyBatis和Atomikos的开发者来说,这是一个宝贵的资源。
本教程主要关注如何将Spring、SpringMVC、MyBatis和Atomikos这四个组件进行整合,以实现多数据源的分布式事务处理。 首先,Spring是Java领域广泛应用的依赖注入(DI)和面向切面编程(AOP)框架,它允许开发者轻松...
Spring、Hibernate和JTA(Java Transaction API)是Java开发者在构建分布式事务解决方案时常用的三大技术。本示例“第二部分spring+hibernate+jta 分布式事务Demo”将帮助我们理解如何在这三个框架之间协同工作,...
本文将详细讲解如何利用Spring Boot、Atomikos、JPA(Java Persistence API)以及MySQL来实现JTA(Java Transaction API)分布式事务。 首先,Spring Boot是一个轻量级的框架,它简化了基于Spring的应用程序开发...
本文将深入探讨如何利用Spring 4.1.7、Atomikos以及MyBatis实现两阶段的分布式事务(2PC,Two-Phase Commit)。这是一套强大的解决方案,特别适用于处理跨多个数据库或服务的复杂业务逻辑。 首先,我们需要理解什么...
Atomikos是开源的事务处理服务提供商,特别适用于分布式事务处理。在3.9版本中,Atomikos提供了强大的JTA(Java Transaction API)支持,能够处理跨多个数据源的ACID(原子性、一致性、隔离性和持久性)事务。在多...
本教程将聚焦于如何利用Spring 3.0、Hibernate ORM框架以及Atomikos这个开源事务管理器来实现高效、可靠的多数据源分布式事务处理。 **Spring 3.0**: Spring是Java开发中最广泛使用的轻量级框架之一,它提供了一个...
本项目使用Spring Boot、Atomikos、JTA(Java Transaction API)、Hibernate和MySQL来实现分布式事务处理和多数据源管理,以确保在多个数据库操作之间保持事务的ACID特性。 首先,Spring Boot作为微服务开发的主流...