`

spring-data-jpa通过Atomikos实现JTA事务

阅读更多

最近刚搭建一个Atomikos实现的JTA,数据库持久层用的spring-data-jpa,底层实现是Hibernate。

一.环境

spring 3.2.6.RELEASE

hibernate 4.2.8.Final

spring-data-jpa 1.4.3.RELEASE

atomikos.version 3.9.2

 

永久链接: http://sgq0085.iteye.com/blog/2001918

 

atomikos在Maven环境中导入依赖如下

<!-- atomikos -->
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jdbc</artifactId>
    <version>3.9.2</version>
</dependency>
<dependency>
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-hibernate3</artifactId>
    <version>3.9.2</version>
</dependency>
<dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>jta</artifactId>
    <version>1.1</version>
</dependency>

 

atomikos依赖结构如下

[INFO] +- com.atomikos:transactions-jdbc:jar:3.9.2:compile
[INFO] |  \- com.atomikos:transactions-jta:jar:3.9.2:compile
[INFO] |     \- com.atomikos:transactions:jar:3.9.2:compile
[INFO] |        \- com.atomikos:transactions-api:jar:3.9.2:compile
[INFO] |           \- com.atomikos:atomikos-util:jar:3.9.2:compile
[INFO] +- com.atomikos:transactions-hibernate3:jar:3.9.2:compile
[INFO] +- javax.transaction:jta:jar:1.1:compile

 

 

二.配置

1.web.xml

  spring.profiles.default为了配合配置文件applicationContext.xml中<beans profile="development">标签,用于管理多套环境

 

<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>development</param-value>
</context-param>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        classpath*:/applicationContext.xml
    </param-value>
</context-param>

<!-- 不一定都用得到 -->
 <filter>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    <init-param>
        <param-name>entityManagerFactoryBeanName</param-name>
        <param-value>entityManagerFactory1</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
 

 

2.persistence.xml

 

<?xml version="1.0" encoding="UTF-8"?>  
<persistence xmlns="http://java.sun.com/xml/ns/persistence"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"  
    version="2.0">  
    <persistence-unit name="PU1" transaction-type="JTA">  
    </persistence-unit>  
    <persistence-unit name="PU2" transaction-type="JTA">  
    </persistence-unit>  
</persistence>  
 

 这里JPA 不在 persistence.xml 文件中配置每个Entity实体类,在后面的applicationContext.xml中通过<property name="packagesToScan" value="com.myapp" />去扫描所有标记@Entity注解的PO。

 

3.application.properties

数据库一个Oracle,一个H2

 

#development
dev.jdbc.driver=oracle.jdbc.driver.OracleDriver
dev.jdbc.url=jdbc:oracle:thin:@192.168.3.129:1521:gtf
dev.jdbc.username=my_app
dev.jdbc.password=my_app

dev.jdbc.driver2=org.h2.jdbcx.JdbcDataSource
dev.jdbc.url2=jdbc:h2:file:~/.h2/myapp;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE
dev.jdbc.username2=sa
dev.jdbc.password2=

databasePlatform.oracle=org.hibernate.dialect.Oracle10gDialect
databasePlatform.h2=org.hibernate.dialect.H2Dialect
 

 

4.applicationContext.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:context="http://www.springframework.org/schema/context" 
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" 
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd">
    
    <description>Spring公共配置</description>

    <!-- 使用annotation 自动注册bean,并检查@Required,@Autowired的属性已被注入 -->
    <context:component-scan base-package="com.gqshao.myapp">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>

    <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
        <property name="transactionTimeout" value="300" />
    </bean>

    <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"
        destroy-method="close">
        <property name="forceShutdown" value="true" />
        <property name="transactionTimeout" value="300" />
    </bean>

    <!-- 事务管理器配置, JTA数据源事务 -->
    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager" ref="atomikosTransactionManager" />
        <property name="userTransaction" ref="atomikosUserTransaction" />
        <property name="allowCustomIsolationLevels" value="true" />
    </bean>

    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

    <!-- 定义aspectj -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <bean id="hibernateJpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <!-- <property name="databasePlatform" value="${oracle.databasePlatform}" /> -->
        <property name="databasePlatform">
            <bean factory-method="getDialect" class="com.gqshao.myapp.common.persistence.Hibernates">
                <constructor-arg ref="dataSource" />
            </bean>
        </property>
        <property name="showSql" value="false" />
        <property name="generateDdl" value="true" />
        <property name="database" value="ORACLE" />
    </bean>

    <!-- Jpa Entity Manager1 配置 -->
    <bean id="entityManagerFactory1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="classpath*:/persistence.xml" />
        <property name="persistenceUnitName" value="PU1" />
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
        <property name="packagesToScan" value="com.gqshao.myapp" />
        <property name="jpaProperties">
            <props>
                <!-- 不开启二级缓存 -->
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.generate_statistics">false</prop>
                <prop key="hibernate.connection.release_mode">after_statement</prop>
                <!-- 命名规则 My_NAME->MyName -->
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <prop key="hibernate.connection.driver_class">oracle.jdbc.xa.client.OracleXADataSource</prop>
                <prop key="hibernate.current_session_context_class">jta</prop>
                <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop>
            </props>
        </property>
    </bean>

    <!-- Spring Data Jpa配置, 扫描base-package下所有继承于Repository<T,ID>的接口 -->
    <jpa:repositories base-package="com.gqshao.myapp.**.dao" transaction-manager-ref="transactionManager"
        entity-manager-factory-ref="entityManagerFactory1" />

    <!-- Jpa Entity Manager2 配置 -->
    <bean id="entityManagerFactory2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="classpath*:/persistence.xml" />
        <property name="persistenceUnitName" value="PU2" />
        <property name="dataSource" ref="dataSource2" />
        <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter" />
        <property name="packagesToScan" value="com.gqshao.myapp" />
        <property name="jpaProperties">
            <props>
                <!-- 不开启二级缓存 -->
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.generate_statistics">false</prop>
                <prop key="hibernate.connection.release_mode">after_statement</prop>
                <!-- 命名规则 My_NAME->MyName -->
                <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <prop key="hibernate.connection.driver_class">oracle.jdbc.xa.client.OracleXADataSource</prop>
                <prop key="hibernate.current_session_context_class">jta</prop>
                <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop>
            </props>
        </property>
    </bean>

    <!-- Spring Data Jpa配置, 扫描base-package下所有继承于Repository<T,ID>的接口 -->
    <jpa:repositories base-package="com.gqshao.myapp.**.repository" transaction-manager-ref="transactionManager"
        entity-manager-factory-ref="entityManagerFactory2" />

    <!-- local development环境 -->
    <beans profile="development">
        <context:property-placeholder location="classpath*:/application.properties"
            ignore-resource-not-found="true" />

        <!-- 数据源1 -->
        <bean id="dataSource" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
            <property name="uniqueResourceName" value="XA1DBMS" />
            <property name="xaDataSourceClassName" value="${dev.jdbc.driver}" />
            <property name="xaProperties">
                <props>
                    <prop key="URL">${dev.jdbc.url}</prop>
                    <prop key="user">${dev.jdbc.username}</prop>
                    <prop key="password">${dev.jdbc.password}</prop>
                </props>
            </property>
            <property name="poolSize" value="10" />
            <property name="minPoolSize" value="10" />
            <property name="maxPoolSize" value="30" />
        </bean>

        <!-- 数据源2 -->
        <bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
            <property name="uniqueResourceName" value="XA2DBMS" />
            <property name="xaDataSourceClassName" value="${dev.jdbc.driver2}" />
            <property name="xaProperties">
                <props>
                    <prop key="URL">${dev.jdbc.url2}</prop>
                    <prop key="user">${dev.jdbc.username2}</prop>
                    <prop key="password">${dev.jdbc.password2}</prop>
                </props>
            </property>
            <property name="poolSize" value="10" />
            <property name="minPoolSize" value="10" />
            <property name="maxPoolSize" value="30" />
        </bean>
    </beans>
</beans>

 

 

 

 

注意:

1.Entity可以被两个EntityManagerFactory同时扫描到

2.通过jpa:repositories的base-package来区分两个EntityManagerFactory维护的持久层在这里这个用dao包,另一个用repository

 

三.测试

 

-------------- 表1 ------------
create table jta_pu1 (
	id char(32),
	description varchar2(255),
    primary key (id)
);

-------------- 表2 ------------
create table jta_pu2 (
	id char(32),
	description varchar2(255),
    primary key (id)
);

 

 

在Oracle中建表1

在H2中表1和表2都建

 

三个Entity类

 

package com.myapp.jta.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

import org.hibernate.annotations.GenericGenerator;

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

	private static final long serialVersionUID = 1L;
	protected String id;

	@Id
	@Column(name = "id", nullable = false)
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
}
 

 

 

package com.myapp.jta.entity;

import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "JTA_PU1")
public class PU1 extends BaseEntity {

    private String description;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
 

 

 

package com.myapp.jta.entity;

import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "JTA_PU2")
public class PU2 extends BaseEntity {

    private String description;

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
 

 

三个数据库持久层类

对应数据源一

 

package com.myapp.jta.dao;

import org.springframework.data.jpa.repository.JpaRepository;

import com.myapp.jta.entity.PU1;

public interface PU1Dao extends JpaRepository<PU1, Integer> {

}
 

 

对应数据源二

 

package com.myapp.jta.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.myapp.jta.entity.PU1;

public interface PU1Repository extends JpaRepository<PU1, Integer> {

}
 
package com.myapp.jta.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.myapp.jta.entity.PU2;

public interface PU2Repository extends JpaRepository<PU2, Integer> {

}
 

 

注意源一对应的包名是dao,源二对应的包名是repository

 

Service比较简单只有一个save,第一次不抛出异常,第二次抛出异常来测试事务回滚。

package com.myapp.jta.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.myapp.jta.dao.PU1Dao;
import com.myapp.jta.entity.PU1;
import com.myapp.jta.entity.PU2;
import com.myapp.jta.repository.PU1Repository;
import com.myapp.jta.repository.PU2Repository;

@Service
@Transactional
public class JTAService {

    @Value("${property.value}")
    private String propertyValue;

    @Autowired
    private PU1Dao pu1Dao;

    @Autowired
    private PU1Repository pu1Repository;

    @Autowired
    private PU2Repository pu2Repository;

    public void save() {
        PU1 pu = new PU1();
        pu.setDescription(propertyValue);
        pu1Dao.save(pu);
        pu1Repository.save(pu);

        PU2 pu2 = new PU2();
        pu2.setDescription("pu2");
        pu2Repository.save(pu2);
        // throw new RuntimeException();
    }
}

 

JUnit4的测试用例

package com.myapp.jta.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/applicationContext.xml"})
@ActiveProfiles("development")
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
public class JTAServiceTest {

    @Autowired
    private JTAService jtaService;

    @Test
    public void savePU1() {
        jtaService.save();
    }
}

 

说明,同一个EntityPU1分别被保存到数据源一和数据源二中,但通过查询数据库可以看到第一次保存的时候id被赋值,第二次保存的时候id没有再做修改。但查询数据库,发现第二次的id与第一次的不同。所以真有同一个Entity保存到两个数据库中的需求时,推荐在保存之前深拷贝一份出来,分别保存。 

 

永久链接: http://sgq0085.iteye.com/blog/2001918

 

注意两个离奇错误

错误1: 数据库必须是DBA,否则出错 (具体哪个权限没有去排查,感兴趣找到了告诉我)

错误2:用maven tomcat7-maven-plugin插件时小心useTestClasspath需要是false

 

DEMO中在使用jdbcdslog的基础上,修改Atomikos核心类,实现JTA管理数据源下SQL的输出。

注意:该方法未经过大量测试,只适用于开发环境,生产环境不可以使用。

分享到:
评论

相关推荐

    SpringBoot 源码解析——源码模块功能分析.docx

    * spring-boot-starter-jta-atomikos:提供了对 Atomikos 事务管理器的支持。 * spring-boot-starter-jta-bitronix:提供了对 Bitronix 事务管理器的支持。 这些启动器之间的交互和协作是非常复杂的,每个启动器都...

    springboot学习思维笔记.xmind

    spring-boot-starter-jta-atomikos spring-boot-starter-jta-bitronix spring-boot-starter-mail spring-boot-starter-mobile spring-boot-starter-mustache spring-boot-starter-...

    03_JPA详解_搭建JPA开发环境和全局事务介绍.zip

    1. **JTA事务管理**: 配置Spring Boot以使用JTA事务管理,需要引入`spring-boot-starter-jta-atomikos`或`spring-boot-starter-jta-bitronix`依赖,并配置相应的事务管理器。 2. **Atomikos和Bitronix**: Atomikos...

    spring-boot-reference.pdf

    29.3. JPA and “Spring Data” 29.3.1. Entity Classes 29.3.2. Spring Data JPA Repositories 29.3.3. Creating and Dropping JPA Databases 29.3.4. Open EntityManager in View 29.4. Using H2’s Web Console ...

    SpringBoot启动器Starters使用及原理解析

    * spring-boot-starter-jta-atomikos:通过Atomikos支持JTA分布式事务处理。 * spring-boot-starter-jta-bitronix:通过Bitronix支持JTA分布式事务处理。 * spring-boot-starter-mail:支持javax.mail模块。 * ...

    第二部分spring+hibernate+jta 分布式事务Demo

    在现代项目中,我们通常会使用更兼容的版本或Spring Data JPA来配合JTA事务。 3. **JTA事务管理**: JTA定义了用户事务API(`UserTransaction`接口)和事务管理器API(`TransactionManager`接口)。`...

    spring jpa

    在压缩包文件 "springjta" 中,可能包含了一些关于 Spring JTA(Java Transaction API)的资料,JTA 是 Java 平台提供的分布式事务处理标准,Spring JPA 在需要时可以与之配合,实现跨多个数据源的事务一致性。...

    mul-at-master.zip

    通过这个项目,开发者可以深入理解Springboot的事务管理、JPA的使用,以及Atomikos如何实现JTA分布式事务。这不仅有助于提升技术水平,也为实际项目中的分布式事务处理提供了参考和实践案例。无论是对新手还是经验...

    springTranction

    10. **JTA(Java Transaction API)事务管理**:对于分布式事务,Spring支持JTA事务管理,如Atomikos、Bitronix等事务管理器,适用于跨越多个数据源的复杂场景。 11. **事务同步**:Spring的Transaction...

    springboot多数据源即分布式事务解决方案

    2. 使用`@Primary`注解标记主数据源,用于Spring Data JPA或其他自动配置。 3. 使用`@Qualifier`注解来指定在Service层注入哪个数据源。 接下来,我们讨论分布式事务。在分布式系统中,事务需要跨越多个数据库或者...

    springboot参考指南

    使用Spring Data JPA和Mongo仓库 x. 67.10. 将Spring Data仓库暴露为REST端点 vii. 68. 数据库初始化 i. 68.1. 使用JPA初始化数据库 ii. 68.2. 使用Hibernate初始化数据库 iii. 68.3. 使用Spring JDBC初始化数据库 ...

    Spring及其各

    对于分布式事务,Spring支持JTA和XAP(如Atomikos、Bitronix)。 7. **安全集成**:Spring Security提供了全面的身份验证和授权机制,可以保护Web应用免受各种攻击。它支持HTTP基本认证、表单登录、OAuth2和其他...

    SpringBoot最新面试题2021年,常见面试题及答案汇总.md

    - **第二种方式**:利用`jta-atomikos`等分布式事务管理库实现。这种方式适用于需要跨数据源的事务管理场景,如微服务架构下的分布式事务。通过Atomikos等工具可以有效地协调不同数据源之间的事务提交与回滚。 ####...

    数据同步spingboot连接多个mysql数据库.zip

    特别是在处理数据库操作时,SpringBoot结合Spring Data JPA或MyBatis等持久层框架,提供了高效且灵活的数据管理方式。本教程将深入探讨如何在SpringBoot项目中实现数据同步,连接并管理多个MySQL数据库。 首先,...

Global site tag (gtag.js) - Google Analytics