├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── example │ │ ├── data │ │ ├── ModelRepository.java │ │ ├── catalog │ │ │ └── ProductRepository.java │ │ └── directory │ │ │ └── PersonRepository.java │ │ └── domain │ │ ├── Model.java │ │ ├── catalog │ │ └── Product.java │ │ └── directory │ │ └── Person.java └── resources │ ├── logback.xml │ └── springContext.xml └── test └── java └── org └── example └── Test.java /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .gradle/ 4 | .idea/ 5 | .settings/ 6 | build/ 7 | target/ 8 | *~ 9 | *.db 10 | *.iml 11 | *.lck 12 | *.log 13 | *.tlog 14 | *.tmp 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 1. Overview [![Code coverage](https://coveralls.io/repos/manish-in-java/spring-data-jta/badge.svg?branch=master&service=github)](https://coveralls.io/github/manish-in-java/spring-data-jta?branch=master) 2 | This sample application demonstrates the use of an external [JTA](http://en.wikipedia.org/wiki/Java_Transaction_API) 3 | transaction manager in a Spring Data JPA application. It uses two database schemas, 4 | saving data to and reading from these schemas, as needed to demonstrate and test the 5 | use of JTA transactions. 6 | 7 | The steps below outline how the JTA transaction manager is configured: 8 | 9 | 1. One or more XA-compliant data source that will manage the underlying data store. 10 | Most libraries provide XA-compliant versions of their drivers. For example, the class 11 | `org.h2.Driver` is the regular (non-XA) JDBC driver for the H2 in-memory database. 12 | The XA-compliant data source for the same database is provided by the class 13 | `org.h2.jdbcx.JdbcDataSource`. Similarly, where `com.mysql.jdbc.Driver` is the regular 14 | JDBC driver class for the MySQL database, `com.mysql.jdbc.jdbc2.optional.MysqlXADataSource` 15 | provides the XA-compliant data source. 16 | 1. Point the `EntityManagerFactory` instance to the XA `DataSource` instance. 17 | This makes sure that the database connections participate in JTA transactions. 18 | 1. Declare an XA transaction manager and an XA user transaction. 19 | 1. Wrap the XA transaction manager in a Spring `JtaTransactionManager`. 20 | 1. Use the Spring `JtaTransactionManager`. 21 | 22 | # 2. JTA providers 23 | 24 | ## 2.1. Atomikos Transaction Essentials 25 | 26 | [Atomikos TransactionEssentials](http://www.atomikos.com/Main/TransactionsEssentials) is 27 | a JTA transaction manager that comes in both open-source and commercial flavours. The 28 | open-source version of Atomikos is included with this application. To see Atomikos in action, 29 | run the bundled integration test as `mvn clean test -D"spring.profiles.active=atomikos"`. 30 | 31 | The open-source version of Atomikos used for this application is available under the 32 | Apache License v2.0. Full licensing details are available on the 33 | [Atomikos website](https://www.atomikos.com/Main/WhichLicenseApplies). 34 | 35 | ## 2.2. Bitronix Transaction Manager 36 | 37 | [Bitronix Transaction Manager](https://github.com/bitronix/btm) is a fully open-source 38 | JTA transaction manager. Run the bundled integration tests as 39 | `mvn clean test -D"spring.profiles.active=bitronix"` to see Bitronix in action. 40 | 41 | The Bitronix version used for this application is available under the 42 | GNU Lesser General Public License v3.0. Full licensing details are available 43 | on Bitronix's [Github repository](https://github.com/bitronix/btm). 44 | 45 | ## 2.3. JBoss Transaction Server 46 | 47 | *JBoss Transaction Server (JBossTS)* was an open-source JTA transaction manager that 48 | used to ship as part of the [JBoss J2EE Application Server](http://jbossas.jboss.org). 49 | Run the bundled integration tests as 50 | `mvn clean test -D"spring.profiles.active=jbossts"` to see JBossTS in action. 51 | 52 | The JBossTS version used for this application is available under the 53 | GNU Lesser General Public License v2.1. 54 | 55 | # 3. License 56 | This sample application and its associated source code in its entirety is being made 57 | available under the following licensing terms. 58 | 59 | Permission is hereby granted, free of charge, to any person obtaining a copy of 60 | this software and associated documentation files (the "Software"), to deal in the 61 | Software without restriction, including without limitation the rights to use, copy, 62 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 63 | and to permit persons to whom the Software is furnished to do so, subject to the 64 | following conditions: 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 67 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 68 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 69 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 70 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 71 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 72 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.example 6 | spring-data-jta 7 | 1.0 8 | jar 9 | Spring Data JTA 10 | A sample application that demonstrates the use of an external JTA transaction manager with Spring Data JPA. 11 | 12 | 13 | 14 | 15 | org.apache.maven.plugins 16 | maven-compiler-plugin 17 | 3.1 18 | 19 | -Xlint:none 20 | ${java.version} 21 | ${java.version} 22 | 23 | 24 | 25 | org.codehaus.mojo 26 | cobertura-maven-plugin 27 | 2.7 28 | 29 | true 30 | 31 | true 32 | 33 | 34 | html 35 | xml 36 | 37 | 38 | 39 | 40 | org.eluder.coveralls 41 | coveralls-maven-plugin 42 | 3.1.0 43 | 44 | 45 | 46 | 47 | 48 | src/main/resources 49 | true 50 | 51 | **/*.properties 52 | **/*.xml 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ch.qos.logback 61 | logback-classic 62 | ${logback.version} 63 | 64 | 65 | org.slf4j 66 | slf4j-api 67 | 68 | 69 | 70 | 71 | 72 | com.atomikos 73 | transactions-hibernate4 74 | ${atomikos.version} 75 | runtime 76 | 77 | 78 | com.atomikos 79 | transactions-jdbc 80 | ${atomikos.version} 81 | runtime 82 | 83 | 84 | com.atomikos 85 | transactions-jta 86 | ${atomikos.version} 87 | runtime 88 | 89 | 90 | 91 | org.codehaus.btm 92 | btm 93 | ${btm.version} 94 | runtime 95 | 96 | 97 | 98 | com.h2database 99 | h2 100 | ${h2.version} 101 | runtime 102 | 103 | 104 | 105 | junit 106 | junit 107 | ${junit.version} 108 | test 109 | 110 | 111 | 112 | org.hibernate 113 | hibernate-entitymanager 114 | ${hibernate.version} 115 | 116 | 117 | org.hibernate 118 | hibernate-validator 119 | ${hibernate.validator.version} 120 | 121 | 122 | 123 | org.jboss.jbossts 124 | jbossjta 125 | ${jbossts.version} 126 | 127 | 128 | 129 | org.postgresql 130 | postgresql 131 | ${postgresql.version} 132 | runtime 133 | 134 | 135 | 136 | org.springframework 137 | spring-context 138 | ${spring.version} 139 | test 140 | 141 | 142 | org.springframework 143 | spring-orm 144 | ${spring.version} 145 | test 146 | 147 | 148 | org.springframework 149 | spring-test 150 | ${spring.version} 151 | test 152 | 153 | 154 | org.springframework 155 | spring-tx 156 | ${spring.version} 157 | 158 | 159 | 160 | org.springframework.data 161 | spring-data-jpa 162 | ${spring.data.jpa.version} 163 | 164 | 165 | 166 | 167 | org.h2.jdbcx.JdbcDataSource 168 | true 169 | true 170 | true 171 | false 172 | false 173 | false 174 | jdbc:h2:mem:catalog;DB_CLOSE_DELAY=-1 175 | jdbc:h2:mem:directory;DB_CLOSE_DELAY=-1 176 | sa 177 | 178 | 1.8 179 | UTF-8 180 | 181 | 4.0.4 182 | 2.1.4 183 | 1.4.192 184 | 5.2.4.Final 185 | 5.2.2.Final 186 | 4.16.6.Final 187 | 4.12 188 | 1.1.7 189 | 9.4-1201-jdbc41 190 | 4.3.20.RELEASE 191 | 1.10.3.RELEASE 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/main/java/org/example/data/ModelRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.data; 2 | 3 | import org.example.domain.Model; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.repository.NoRepositoryBean; 6 | 7 | /** 8 | * Contract for data access operations on an {@link Model}. 9 | */ 10 | @NoRepositoryBean 11 | public interface ModelRepository extends JpaRepository 12 | { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/example/data/catalog/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.data.catalog; 2 | 3 | import org.example.data.ModelRepository; 4 | import org.example.domain.catalog.Product; 5 | 6 | /** 7 | * Contract for data access operations on an {@link Product}. 8 | */ 9 | public interface ProductRepository extends ModelRepository 10 | { 11 | /** 12 | * Finds a product by its name. 13 | * 14 | * @param name The product name to find. 15 | * @return A {@link Product} if {@code name} 16 | * is found, {@code null} otherwise. 17 | */ 18 | Product findByName(String name); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/example/data/directory/PersonRepository.java: -------------------------------------------------------------------------------- 1 | package org.example.data.directory; 2 | 3 | import org.example.data.ModelRepository; 4 | import org.example.domain.directory.Person; 5 | 6 | /** 7 | * Contract for data access operations on an {@link Person}. 8 | */ 9 | public interface PersonRepository extends ModelRepository 10 | { 11 | /** 12 | * Finds a user by its name. 13 | * 14 | * @param name The user name to find. 15 | * @return A {@link Person} if {@code name} 16 | * is found, {@code null} otherwise. 17 | */ 18 | Person findByName(String name); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/example/domain/Model.java: -------------------------------------------------------------------------------- 1 | package org.example.domain; 2 | 3 | import org.hibernate.annotations.Generated; 4 | import org.hibernate.annotations.GenerationTime; 5 | 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.GenerationType; 8 | import javax.persistence.Id; 9 | import javax.persistence.MappedSuperclass; 10 | import java.io.Serializable; 11 | 12 | /** 13 | * Represents a domain entity. 14 | */ 15 | @MappedSuperclass 16 | public abstract class Model implements Serializable 17 | { 18 | @Generated(GenerationTime.INSERT) 19 | @GeneratedValue(strategy = GenerationType.AUTO) 20 | @Id 21 | private Long id; 22 | 23 | /** 24 | * Gets the unique identifier for this entity instance. 25 | * 26 | * @return The unique identifier for this entity instance. 27 | */ 28 | public Long getID() 29 | { 30 | return this.id; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/example/domain/catalog/Product.java: -------------------------------------------------------------------------------- 1 | package org.example.domain.catalog; 2 | 3 | import org.example.domain.Model; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.Table; 8 | import javax.validation.constraints.NotNull; 9 | 10 | /** 11 | * Represents a product. 12 | */ 13 | @Entity 14 | @Table(name = "product") 15 | public class Product extends Model 16 | { 17 | @Column(name = "name") 18 | @NotNull 19 | private String name; 20 | 21 | /** 22 | * Gets the product name. 23 | * 24 | * @return The product name. 25 | */ 26 | public String getName() 27 | { 28 | return name; 29 | } 30 | 31 | /** 32 | * Sets the product name. 33 | * 34 | * @param name The product name. 35 | */ 36 | public void setName(final String name) 37 | { 38 | this.name = name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/example/domain/directory/Person.java: -------------------------------------------------------------------------------- 1 | package org.example.domain.directory; 2 | 3 | import org.example.domain.Model; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.Table; 8 | import javax.validation.constraints.NotNull; 9 | 10 | /** 11 | * Represents a person. 12 | */ 13 | @Entity 14 | @Table(name = "person") 15 | public class Person extends Model 16 | { 17 | @Column(name = "name") 18 | @NotNull 19 | private String name; 20 | 21 | /** 22 | * Gets the person's ame. 23 | * 24 | * @return The person's name. 25 | */ 26 | public String getName() 27 | { 28 | return name; 29 | } 30 | 31 | /** 32 | * Sets the person's name. 33 | * 34 | * @param name The person's name. 35 | */ 36 | public void setName(final String name) 37 | { 38 | this.name = name; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%6p] %m%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/springContext.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /src/test/java/org/example/Test.java: -------------------------------------------------------------------------------- 1 | package org.example; 2 | 3 | import org.example.data.catalog.ProductRepository; 4 | import org.example.data.directory.PersonRepository; 5 | import org.example.domain.catalog.Product; 6 | import org.example.domain.directory.Person; 7 | import org.junit.Assert; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.test.annotation.Rollback; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | /** 16 | * Integration test for JTA transactions 17 | */ 18 | @ContextConfiguration(locations = "classpath:springContext.xml") 19 | @Rollback 20 | @RunWith(SpringJUnit4ClassRunner.class) 21 | @Transactional 22 | public class Test 23 | { 24 | private static final String PERSON_NAME = "yoda"; 25 | private static final String PRODUCT_NAME = "Light Sabre"; 26 | 27 | @Autowired 28 | private PersonRepository personRepository; 29 | @Autowired 30 | private ProductRepository productRepository; 31 | 32 | /** 33 | * Tests a JTA transaction. 34 | */ 35 | @org.junit.Test 36 | public void test() 37 | { 38 | final Person person = new Person(); 39 | person.setName(PERSON_NAME); 40 | 41 | personRepository.save(person); 42 | 43 | final Product product = new Product(); 44 | product.setName(PRODUCT_NAME); 45 | 46 | productRepository.save(product); 47 | 48 | final Person retrievedPerson = personRepository.findByName(PERSON_NAME); 49 | Assert.assertNotNull(retrievedPerson); 50 | Assert.assertNotNull(retrievedPerson.getID()); 51 | Assert.assertEquals(PERSON_NAME, retrievedPerson.getName()); 52 | 53 | final Product retrievedProduct = productRepository.findByName(PRODUCT_NAME); 54 | Assert.assertNotNull(retrievedProduct); 55 | Assert.assertNotNull(retrievedProduct.getID()); 56 | Assert.assertEquals(PRODUCT_NAME, retrievedProduct.getName()); 57 | } 58 | } 59 | --------------------------------------------------------------------------------