spring boot,JPA和Atomikos实现分布式事务

一、实例描述和实体模型

     我们想在同一时间两个不同的数据库保存两个实体,这个操作需要事务。因此,在这个例子中,我们有一个Customer实体,它将第一个持久化到数据库中,而Order实体将被持久化到第二个数据库中。这两个实体非常简单,这个实例仅仅是一个示范。



这个结果实现如下:值得注意的是,它是属于两个不同的包,原因有两点:

1、项目呈现上下级逻辑分离的

2、每一个repository将扫描包含实体的包,并且进行管理。



package com.at.mul.domain.customer;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Entity
@Table(name = "customer")
@Data
@EqualsAndHashCode(exclude = { "id" })
public class Customer {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "name", nullable = false)
private String name;

@Column(name = "age", nullable = false)
private Integer age;

}

package com.at.mul.domain.order;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Entity
@Table(name = "orders")
@Data
@EqualsAndHashCode(exclude = { "id" })
public class Order {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "code", nullable = false)
private Integer code;

@Column(name = "quantity", nullable = false)
private Integer quantity;

}

对于注解@Data和@EqualsAndHashCode请看Lombok



二、写repositories接口

在这个事例中它是一个标准,这里是需要注意的是我写了两个接口在两个不同的包,这个原因在下一个步骤将会解释:

package com.at.mul.repository.customer;

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

import com.at.mul.domain.customer.Customer;

public interface CustomerRepository extends JpaRepository<Customer, Integer> {

}

package com.at.mul.repository.order;

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

import com.at.mul.domain.order.Order;

public interface OrderRepository extends JpaRepository<Order, Integer> {

}
三、写配置类:

     这里有一点有兴趣的事,@DependsOn("transactionManager")注解不是强制的,但是在测试启动时我需要去掉若干的警告,像logs里面的WARNING: transaction manager not running?。下一个注解@EnableJpaRepositories才是重要的。

1、情况一是对于注解组件进行包扫描(repository接口),并且在我的实例中,我想仅仅repositories

customer (相反的就是repositories order)

2、情况二是实体管理者去管理实体,在我的实例中,customerEntityManager管理customer相关操作并且orderEntityManager管理order的相关操作

3、情况三是事务管理器被使用,在我的实例中transactionManager定义在MainConfig类中。这是需要对于每一个@EnableJpaRepositories 获取的工作事务都是同一个。

package com.at.mul;

import java.util.HashMap;

import javax.sql.DataSource;

import org.h2.jdbcx.JdbcDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

import com.at.mul.repository.customer.CustomerDatasourceProperties;
import com.atomikos.jdbc.AtomikosDataSourceBean;

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(basePackages = "com.at.mul.repository.customer", entityManagerFactoryRef = "customerEntityManager", transactionManagerRef = "transactionManager")
@EnableConfigurationProperties(CustomerDatasourceProperties.class)
public class CustomerConfig {

@Autowired
private JpaVendorAdapter jpaVendorAdapter;

@Autowired
private CustomerDatasourceProperties customerDatasourceProperties;

@Bean(name = "customerDataSource", initMethod = "init", destroyMethod = "close")
public DataSource customerDataSource() {
JdbcDataSource h2XaDataSource = new JdbcDataSource();
h2XaDataSource.setURL(customerDatasourceProperties.getUrl());

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(h2XaDataSource);
xaDataSource.setUniqueResourceName("xads1");
return xaDataSource;
}

@Bean(name = "customerEntityManager")
@DependsOn("transactionManager")
public LocalContainerEntityManagerFactoryBean customerEntityManager() throws Throwable {

HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
properties.put("javax.persistence.transactionType", "JTA");

LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setJtaDataSource(customerDataSource());
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
entityManager.setPackagesToScan("com.at.mul.domain.customer");
entityManager.setPersistenceUnitName("customerPersistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}

}

package com.at.mul;

import java.util.HashMap;

import javax.sql.DataSource;

import org.h2.jdbcx.JdbcDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;

import com.at.mul.repository.order.OrderDatasourceProperties;
import com.atomikos.jdbc.AtomikosDataSourceBean;

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(basePackages = "com.at.mul.repository.order", entityManagerFactoryRef = "orderEntityManager", transactionManagerRef = "transactionManager")
@EnableConfigurationProperties(OrderDatasourceProperties.class)
public class OrderConfig {

@Autowired
private JpaVendorAdapter jpaVendorAdapter;

@Autowired
private OrderDatasourceProperties orderDatasourceProperties;

@Bean(name = "orderDataSource", initMethod = "init", destroyMethod = "close")
public DataSource orderDataSource() {
JdbcDataSource h2XaDataSource = new JdbcDataSource();
h2XaDataSource.setURL(orderDatasourceProperties.getUrl());

AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(h2XaDataSource);
xaDataSource.setUniqueResourceName("xads2");
return xaDataSource;
}

@Bean(name = "orderEntityManager")
public LocalContainerEntityManagerFactoryBean orderEntityManager() throws Throwable {

HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
properties.put("javax.persistence.transactionType", "JTA");

LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setJtaDataSource(orderDataSource());
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
entityManager.setPackagesToScan("com.at.mul.domain.order");
entityManager.setPersistenceUnitName("orderPersistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}

}

另一个重要的事,这里LocalContainerEntityManagerFactoryBean的定义:

1、@bean注解有获取一个name,他是在@EnableJpaRepositories注解中进行指定的。

2、你需要设置一些属性到JpaPropertyMap中,详细的说,你需要标注transaction是JTA和JTA平台是AtomikosJtaPlatform.class.getName()

我的实例不能正常执行的就是我为什么不设置第二个属性的原因。Dave Syer写道"我看了Atomikos范围之外Hibernate4不能工作",因此你需要实现类去设置hibernate.transaction.jta.platform属性,依我看来,这不是一个很好的文档,但是庆幸Oliver Gierke发现了另一个关于这个标题的文章 StackOverflow discussion 。如果你是用的是另一个JTA提供者,这个可能对你有用this useful.

注意:文章到这里还没有完,由于篇幅限制,完整内容请到hongfu951博客上查看

完整内容URL地址:用多数据库spring boot,spring data JPA和Atomikos实现分布式事务