2019独角兽企业重金招聘Python工程师标准>>>

A couple of weeks ago I was evaluating the possibility to use Spring Boot, Spring Data JPA and Atomikos for distributed transactions involving multiple databases. After looking at the Spring blog article (which involves one database and ActiveMQ) and having done some attempts, I could not get it to work with two databases. The configuration seemed fine, but the Entity Manager did not get notified when persisting my entities. So I wrote this question on StackOverflow, which has been answered directly by Dave Syer and Oliver Gierke. This post is to share and discuss the solution.

Description of the case and entities model

We want to be able to save two entities at the same time into two different databases; the operation must be transactional. So, in this example, we have a Customer entity, which is persisted in the first database, and an Order entity which is persisted in the second database. The two entities are very simple, as they serve only as a demonstration.

The resulting implementation is the following. It's worth noting that they belong to two different packages, for two main reasons:

  1. it's a logical separation that gives order to the project
  2. each repository will scan packages containing only entities that it will be going to manage
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;}

See Lombok for annotations like @Data and @EqualsAndHashCode

Write repositories interfaces

Also in this case it's standard, the only thing to notice is that I put the two interfaces in two different packages. The reason is explained in the next step.

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> {}

Write configuration classes

This is where it becomes interesting. The @DependsOn("transactionManager") annotation is not mandatory, but I needed this to get rid of several warnings at tests (or application) startup, like WARNING: transaction manager not running? in the logs. The next annotation @EnableJpaRepositories is more important:

  • it specifies which are the packages to scan for annotated components (repository interfaces), and in my case I wanted only repositories related to the customer (and conversely to the order).
  • it specifies which is the entity manager to be used to manage entities, in my case the customerEntityManager for customer related operations and orderEntityManager for order related operations
  • it specifies the transaction manager to be used, in my case the transactionManager defined in the MainConfig class. This needs to be the same for every @EnableJpaRepositories to get distributed transactions working
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 {@Autowiredprivate JpaVendorAdapter jpaVendorAdapter;@Autowiredprivate 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 {@Autowiredprivate JpaVendorAdapter jpaVendorAdapter;@Autowiredprivate 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;}}

Another important thing here is the definition of the LocalContainerEntityManagerFactoryBean.

  • the @Bean annotation has a given name, that is the one specified in the @EnableJpaRepositories annotation.
  • you need to set some properties to the JpaPropertyMap, in particular you need to say that the transaction type is JTA and that the jta platform is AtomikosJtaPlatform.class.getName()

Not setting the second property was the reason why I could not get it work. As Dave Syer wrote "It seems Hibernate4 doesn't work with Atomikos out of the box", so you need to implement the class to be set as hibernate.transaction.jta.platform property by yourself. In my opinion this is not very well documented, but fortunately Oliver Gierke found another StackOverflow discussion about this topic. If you are using another JTA provider, you may find this useful.

Write the AbstractJtaPlatform implementation

As said, this is the most important step, as we need to write the implementation of that class by ourselves since Hibernate does not provide it. Here is the resulting code:

package com.at.mul;import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;public class AtomikosJtaPlatform extends AbstractJtaPlatform {private static final long serialVersionUID = 1L;static TransactionManager transactionManager;static UserTransaction transaction;@Overrideprotected TransactionManager locateTransactionManager() {return transactionManager;}@Overrideprotected UserTransaction locateUserTransaction() {return transaction;}}

Write the main configuration class

Also in this case it's a pretty standard class, with @EnableTransactionManagement annotation and Atomikos bean definitions. The only very important thing to notice is that we need to set AtomikosJtaPlatform.transactionManager and AtomikosJtaPlatform.transaction attributes.

package com.at.mul;import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;@Configuration
@ComponentScan
@EnableTransactionManagement
public class MainConfig {@Beanpublic PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {return new PropertySourcesPlaceholderConfigurer();}@Beanpublic JpaVendorAdapter jpaVendorAdapter() {HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();hibernateJpaVendorAdapter.setShowSql(true);hibernateJpaVendorAdapter.setGenerateDdl(true);hibernateJpaVendorAdapter.setDatabase(Database.H2);return hibernateJpaVendorAdapter;}@Bean(name = "userTransaction")public UserTransaction userTransaction() throws Throwable {UserTransactionImp userTransactionImp = new UserTransactionImp();userTransactionImp.setTransactionTimeout(10000);return userTransactionImp;}@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")public TransactionManager atomikosTransactionManager() throws Throwable {UserTransactionManager userTransactionManager = new UserTransactionManager();userTransactionManager.setForceShutdown(false);AtomikosJtaPlatform.transactionManager = userTransactionManager;return userTransactionManager;}@Bean(name = "transactionManager")@DependsOn({ "userTransaction", "atomikosTransactionManager" })public PlatformTransactionManager transactionManager() throws Throwable {UserTransaction userTransaction = userTransaction();AtomikosJtaPlatform.transaction = userTransaction;TransactionManager atomikosTransactionManager = atomikosTransactionManager();return new JtaTransactionManager(userTransaction, atomikosTransactionManager);}}

Resources

Here is the resulting structure of the project:

You can see the full source code here: https://github.com/fabiomaffioletti/mul-at, The master branch uses in memory database. Checkout branch named mysql-db to use real databases (see application.properties to tweak your database connection data).

转载于:https://my.oschina.net/pangzhuzhu/blog/318126

Distributed transactions with multiple databases, Spring Boot, Spring Data JPA and Atomikos相关推荐

  1. Spring Boot Spring MVC 异常处理的N种方法

    默认行为 根据Spring Boot官方文档的说法: For machine clients it will produce a JSON response with details of the e ...

  2. spring boot + spring batch 读数据库文件写入文本文件读文本文件写入数据库

    好久没有写博客,换了一家新公司,原来的公司用的是spring,现在这家公司用的是spring boot.然后,项目组布置了一个任务,关于两个数据库之间的表同步,我首先想到的就是spring batch ...

  3. Spring Boot Spring MVC 异常处理的N种方法 1

    github:https://github.com/chanjarste... 参考文档: Spring Boot 1.5.4.RELEASE Documentation Spring framewo ...

  4. Spring - Spring Boot Spring Cloud

    Spring -> Spring Boot > Spring Cloud 这几天刚刚上班,公司用的是Spring Cloud,接触不多.我得赶快学起来. 想学习就必须得知道什么是微服务,什 ...

  5. Spring Boot下使用JPA报错:'hibernate.dialect' not set的解决办法

    问题现象: Spring Boot下使用JPA报错:'hibernate.dialect' not set 原因是: 没有设置数据库方言导致的 解决方案: 1.如果配置文件格式为application ...

  6. springboot jwt token前后端分离_基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目...

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  7. Spring Boot + Spring Data + Elasticsearch实例

    在本文中,我们将讨论"如何创建Spring Boot + Spring Data + Elasticsearch范例". 本文中使用的工具: Spring Boot 1.5.1.R ...

  8. java小马哥百度网盘_思否编程(小马哥):Java 微服务实践 - Spring Boot / Spring Cloud全套,完整版下载 - VIPC6资源网...

    小马哥 Java 微服务实践 – Spring Boot 系列 pptx segmentfault-lessons-master 03Java 微服务实践 – Spring Boot 系列(三)Web ...

  9. Spring Boot + Spring Security + JWT + 微信小程序登录

    Spring Boot + Spring Security + JWT + 微信小程序登录整合教程 参考文章 文章目录 整合思想 整合步骤 1. AuthenticationToken 2. Auth ...

最新文章

  1. 什么?听说这四个概念,很多 Java 老手都说不清!
  2. C++中无符号数与有符号数的转换
  3. linux明日命令(6):rm命令
  4. 对于并列的TextField实现同步控制
  5. python2 dict 乱序_为什么我的python dict变得无序?
  6. 华为OJ: 公共字符串计算
  7. java timezone_Java TimeZone getAvailableIDs()方法与示例
  8. LeetCode - Combinations
  9. 中国软件开发project师之痛
  10. (17)VHDL实现编码器
  11. 关于微信小程序中uView中通过packer选择器修改表单无法触发form组件的表单验证的问题
  12. 最新PHP搞笑文字表情包在线制作网站源码
  13. python counter_如何获得按输入顺序排序的python Counter输出?
  14. 送书 | 教你爬取电影天堂数据
  15. ural 1104. Don’t Ask Woman about Her Age
  16. 大疆A型板使用经验分享(八)——FreeRTOS操作系统的使用
  17. Linux下Mysql 5.7的安装及远程连接配置
  18. (void (*Visit)(const ElemType ))
  19. 考研日记----9.08-----中秋快乐
  20. HGAME2021Week1 Writeup

热门文章

  1. 玩转HTML5+跨平台开发[4] HTML表格标签
  2. if...else..的错误用法
  3. UnityShader之Shader分类篇【Shader资料2】
  4. 开发、测试与QA的区别以及其他
  5. 安卓APP_ 控件(7)——Toolbar栏目样式
  6. CSDN怎么转载别人的博客
  7. python处理excel的方法有哪些_python简单处理excel方法
  8. matlab画泡面图,MATLAB中,( )函数可以保存图像并指定为图像文件格式。
  9. LinkedHashMap的使用
  10. NLog日志框架使用探究