JPA & XA

本文介绍了如何使用Spring 3和Atomikos实现跨不同数据源的XA事务管理,包括Oracle和Sybase等数据库,以及如何配置实体管理器、数据源和事务管理器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

After I wrote a blog about Spring & JPA settings: http://jellyfish.iteye.com/admin/blogs/899281, a friend asked me a question about XA on JPA.

 

Last time I did XA was the year 2002, so I figured maybe it's time to revisit this topic to see how things have changed since then. Well, a lot, frankly.

 

I did XA across two data sources, db2 and sybase; and across a data source and a JMS queue. I am using Spring 3 now, the first thing that I noticed is that for some unknown reason, Spring 3 drop the JOTM support (Spring 2.5 had it). Does anyone know the reason behind it?

 

So I am going to use Atomikos's libs for XA. I started from the above blog to see how much I need to change in order to get XA working.

 

First, the persistence file needs a little change (marked in red):

 

<?xml version="1.0" encoding="UTF-8" ?>

<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" xmlns="http://java.sun.com/xml/ns/persistence">

    <persistence-unit name="myPersistenceUnit" transaction-type="JTA">
        <mapping-file>my/test/dao/jpa/hibernate/book.jpa.hibernate.xml</mapping-file>
    </persistence-unit>
</persistence>

 I rename this file to persistence.jta.xml, since we need to reference this file later. Next, we need to change the Spring xml file, since the change is pretty dramatic, I just paste the entire file and then explain the beans one by one later:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="bookService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="springTransactionManager"/>
        <property name="target" ref="bookServiceTarget"/>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED, -Exception</prop>
            </props>
        </property>
    </bean>

    <bean id="userTransactionService"
          class="com.atomikos.icatch.config.UserTransactionServiceImp"
          init-method="init" destroy-method="shutdownForce">
        <constructor-arg>
            <props>
                <prop key="com.atomikos.icatch.service">
                    com.atomikos.icatch.standalone.UserTransactionServiceFactory
                </prop>
            </props>
        </constructor-arg>
    </bean>

    <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManager">
            <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
                  init-method="init" destroy-method="close" depends-on="userTransactionService">
                <property name="forceShutdown" value="false"/>
                <property name="startupTransactionService" value="false"/>
            </bean>
        </property>
        <property name="userTransaction">
            <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" depends-on="userTransactionService">
                <property name="transactionTimeout" value="5"/>
            </bean>
        </property>
        <property name="allowCustomIsolationLevels" value="true"/>
    </bean>

    <bean id="bookServiceTarget" class="my.test.dao.jpa.hibernate.BookService">
        <property name="dao1" ref="bookDao1"/>
        <property name="dao2" ref="bookDao2"/>
    </bean>
    
    <bean id="bookDao1" class="my.test.dao.jpa.hibernate.BookDaoJpaHibernateJtaImpl">
        <property name="entityManager">
            <bean class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
                <property name="entityManagerFactory" ref="myEmf1"/>
            </bean>
        </property>
    </bean>

    <bean id="bookDao2" class="my.test.dao.jpa.hibernate.BookDaoJpaHibernateJtaImpl">
        <property name="entityManager">
            <bean class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
                <property name="entityManagerFactory" ref="myEmf2"/>
            </bean>
        </property>
    </bean>

    <bean id="myEmf1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
        <property name="dataSource" ref="mydataSource1"/>
        <property name="persistenceXmlLocation" value="classpath:my/test/dao/jpa/hibernate/persistence.jta.xml"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!-- <property name="showSql" value="true"/> -->
                <property name="generateDdl" value="false"/>
                <property name="database" value="ORACLE"/>
                <property name="databasePlatform" value="org.hibernate.dialect.OracleDialect"/>
            </bean>
        </property>
        <property name="jpaProperties">
            <map>
                <entry key="hibernate.transaction.manager_lookup_class"
                       value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup"/>
            </map>
        </property>
    </bean>

    <bean id="myEmf2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
        <property name="dataSource" ref="mydataSource2"/>
        <property name="persistenceXmlLocation" value="classpath:my/test/dao/jpa/hibernate/persistence.jta.xml"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        </property>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!-- <property name="showSql" value="true"/> -->
                <property name="generateDdl" value="false"/>
                <property name="database" value="ORACLE"/>
                <property name="databasePlatform" value="org.hibernate.dialect.OracleDialect"/>
            </bean>
        </property>
        <property name="jpaProperties">
            <map>
                <entry key="hibernate.transaction.manager_lookup_class"
                       value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup"/>
            </map>
        </property>       
    </bean>

    <bean id="mydataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource"/>
        <property name="uniqueResourceName" value="mydataSource1"/>
        <property name="poolSize" value="1"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">jdbc:oracle:thin:@***</prop>
                <prop key="user">***</prop>
                <prop key="password">***</prop>
            </props>
		</property>
    </bean>

    <bean id="mydataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
        <property name="xaDataSourceClassName" value="oracle.jdbc.xa.client.OracleXADataSource"/>
        <property name="uniqueResourceName" value="mydataSource2"/>
        <property name="poolSize" value="1"/>
        <property name="xaProperties">
            <props>
                <prop key="URL">jdbc:oracle:thin:@***</prop>
                <prop key="user">***</prop>
                <prop key="password">***</prop>
            </props>
		</property>
    </bean>    
</beans>

 Let's go through this from bottom up. The last two beans are (different) data sources (I am using Oracle), make sure you use XA jdbc drivers. We want to test out the transaction management across two different databases (could be oracle/sybase/db2/sqlserver).

 

The next two beans are entity manager factor (emf). We first changed the persistence.xml new path, then add a "jpaProperties" entry according to Atomikos's document:

http://www.atomikos.com/Documentation/SpringIntegration

http://www.atomikos.com/Documentation/HibernateIntegration

 

Now the DAO class is little bit problematic since the emf was injected through @PersistenceContext. Let's remove this annotation and create a setter, like this:

public class BookDaoJpaHibernateJtaImpl  implements BookDao
{
    private EntityManager em;

    public void setEntityManager(EntityManager em)
    {
        this.em = em;
    }

    // other methods are the same
}

 So the next two DAO beans (bookDao1 and bookDao2) use this class. The emf fields are injected separately.

 

Next, we create a new class that uses the above two DAO classes:

package my.test.dao.jpa.hibernate;

import my.test.Book;
import my.test.BookDao;

public class BookService
{
    private BookDao dao1;
    private BookDao dao2;

    public void save(Book book)
    {

        dao2.save(book);
        dao1.save(book);
    }

    public void setDao2(BookDao dao2)
    {
        this.dao2 = dao2;
    }

    public void setDao1(BookDao dao1)
    {
        this.dao1 = dao1;
    }
}

 and we have the corresponding bean bookServiceTarget.

 

The next two beans are Atomikos related, together they define the transaction manager. Please check Atomikos document.

 

Finally, the first bean bookService stitch the bookServiceTarget and the transaction manager together.

 

During testing, I created two same tables in each database, add an unique index for book_id on the table in the second database. Then insert a simple book object through book service. The first run should be fine since both database tables are empty. The second run should fail because the same id

was tried to be inserted into the table in the second database. After the second run, both tables should have count 1 on rows (i.e., the second run got rolled back).

在使用 Apache ShardingSphere 的情况下,与 Spring Data JPA 进行集成时,主要需要关注以下几个方面:数据源配置、分片策略定义、事务管理以及兼容性处理。 ### 数据源配置 ShardingSphere 提供了对 JDBC 的支持,并可以通过 `ShardingDataSourceFactory` 来创建分片数据源。在 Spring Boot 应用中,可以将其作为默认的数据源注入到 JPA 中[^1]。以下是一个典型的配置示例: ```yaml spring: shardingsphere: datasource: names: ds0,ds1 ds0: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ds0 username: root password: root ds1: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ds1 username: root password: root rules: sharding: tables: user: actual-data-nodes: ds$-&gt;{0..1}.user_$-&gt;{0..1} table-strategy: standard: sharding-column: user_id sharding-algorithm-name: user-table-inline key-generator: column: user_id type: SNOWFLAKE sharding-algorithms: user-table-inline: type: INLINE props: algorithm-expression: user_$-&gt;{user_id % 2} ``` 该配置指定了两个数据源 `ds0` 和 `ds1`,并为 `user` 表定义了分片规则,根据 `user_id` 字段进行分片。 ### 分片策略定义 在实际业务场景中,通常需要根据业务逻辑定义分片键(sharding key)和分片算法。例如,使用 `INLINE` 算法将用户数据均匀分布到多个数据库或表中。此外,还可以自定义分片策略类实现更复杂的逻辑。 ### 事务管理 ShardingSphere 支持本地事务和分布式事务。在 JPA 场景下,若涉及跨库操作,建议使用 XA 或 BASE 模式来保证事务一致性。Spring Boot 可以通过配置事务管理器来启用分布式事务支持,如 Atomikos 或 Bitronix。 ### 兼容性处理 由于 JPA 是基于 ORM 的抽象层,某些 SQL 语法可能无法直接适配到分片环境中。此时需要特别注意以下几点: - 避免使用跨分片的 JOIN 查询,应尽量通过应用层聚合数据。 - 使用 `@Query` 注解时,确保 SQL 语句符合分片路由规则。 - 对于分页查询,需使用 ShardingSphere 提供的分页优化机制,避免性能问题。 ### 示例代码:配置 ShardingSphere 数据源与 JPA 整合 ```java @Configuration @EnableJpaRepositories(basePackages = &quot;com.example.repository&quot;) public class ShardingSphereJpaConfig { @Bean public DataSource dataSource() throws SQLException { return ShardingSphereDataSourceFactory.createDataSource(getShardingRuleConfiguration()); } private ShardingRuleConfiguration getShardingRuleConfiguration() { ShardingRuleConfiguration config = new ShardingRuleConfiguration(); config.getTableRuleConfigs().add(getUserTableRuleConfig()); config.getBindingTableGroups().add(&quot;user&quot;); return config; } private TableRuleConfiguration getUserTableRuleConfig() { TableRuleConfiguration tableRule = new TableRuleConfiguration(&quot;user&quot;, &quot;ds${0..1}.user_${0..1}&quot;); tableRule.setTableStrategy(new StandardShardingStrategyConfiguration(&quot;user_id&quot;, &quot;user-table-inline&quot;)); return tableRule; } } ``` ### 注意事项 - **SQL 限制**:JPA 自动生成的 SQL 可能不适用于所有分片场景,特别是复杂查询和子查询。 - **主键生成**:推荐使用 Snowflake 或 UUID 等全局唯一主键策略,避免冲突。 - **性能调优**:合理设置连接池参数,优化 SQL 执行效率,尤其在高并发场景下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值