欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-23 11:23:06
...

最近刚搭建一个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的输出。

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