- Transaction Manager for Microservices Developer Guide
- Develop Applications with XA
- Develop JAX-RS Apps with XA
- Configure JPA-Based Java App as Transaction Participant
- Configure JPA-Based Java App with an XA-Compliant Resource Manager
7.11.4.1 Configure JPA-Based Java App with an XA-Compliant Resource Manager
Use the information provided in this section to configure Hibernate or EclipseLink as the JPA provider for your Helidon or Spring Boot applications which participates in an XA transaction when you use an XA-compliant resource manager.
- Configure property values for the MicroTx client library properties.
The following example provides sample values for the properties. Provide the values based on your environment.
oracle.tmm.TcsConnPoolSize = 15 oracle.tmm.CallbackUrl = https://bookTicket-app:8081 oracle.tmm.PropagateTraceHeaders = true oracle.tmm.TransactionTimeout = 60000 oracle.tmm.xa.XaSupport = true
Ensure that
oracle.tmm.xa.XaSupport
is set totrue
.For details about each property and other optional properties, see Configure Library Properties for JAX-RS and Node.js Apps.
- Include the MicroTx Java library file as a maven dependency in the application's
pom.xml
file. The following sample code is for the 24.4 release. Provide the correct version, based on the release version that you want to use.-
In Jakarta EE8 environments, such as Helidon 2.x, use the
TmmLib
file.<dependency> <groupId>com.oracle.tmm.jta</groupId> <artifactId>TmmLib</artifactId> <version>24.4</version> </dependency>
-
In Jakarta EE9 environments, such as Helidon 3.x applications, use the
TmmLib-jakarta
file.<dependency> <groupId>com.oracle.tmm.jta</groupId> <artifactId>TmmLib-jakarta</artifactId> <version>24.4</version> </dependency>
-
- Perform this task only for Spring Boot applications that use the JAX-RS API. Create a
.java
file in the folder that contains your application code to initialize anXADataSourceConfig
object. TheXADataSourceConfig
class contains methods to create custom data source and entity manager factory objects.The following example code shows how you can initialize the library in within the
XADataSourceConfig
class, create a custom data source nameddepartmentDataSource
, and create an entity manager factory object namedemf
. You can create a similar code for your application.The custom data source object contains details to connect with the resource manager. It is the responsibility of the application developer to ensure that an XA-compliant JDBC driver and required parameters are set up while creating a custom data source object.
package com.oracle.mtm.sample; import oracle.tmm.common.TrmConfig; import oracle.tmm.jta.jpa.hibernate.HibernateXADataSourceConnectionProvider; import oracle.ucp.jdbc.PoolDataSourceFactory; import oracle.ucp.jdbc.PoolXADataSource; import org.hibernate.jpa.HibernatePersistenceProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; import java.sql.SQLException; import java.util.Properties; @Configuration @EnableTransactionManagement public class XADataSourceConfig { @Value("${spring.xads.datasource.url}") private String url; @Value("${spring.xads.datasource.username}") private String username; @Value("${spring.xads.datasource.password}") private String password; @Value("${spring.xads.datasource.oracleucp.min-pool-size}") private String minPoolSize; @Value("${spring.xads.datasource.oracleucp.initial-pool-size:10}") private String initialPoolSize; @Value("${spring.xads.datasource.oracleucp.max-pool-size}") private String maxPoolSize; @Value("${spring.xads.datasource.oracleucp.data-source-name}") private String dataSourceName; @Value("${spring.xads.datasource.oracleucp.connection-pool-name}") private String connectionPoolName; @Value("${spring.xads.datasource.oracleucp.connection-factory-class-name:oracle.jdbc.xa.client.OracleXADataSource}") private String connectionFactoryClassName; //Create a custom data source object. Provide credentials and other details to connect to the resource manager. @Bean(name = "departmentDataSource") @Primary public DataSource getDataSource() { DataSource pds = null; try { pds = PoolDataSourceFactory.getPoolXADataSource(); ((PoolXADataSource) pds).setConnectionFactoryClassName(connectionFactoryClassName); ((PoolXADataSource) pds).setURL(url); ((PoolXADataSource) pds).setUser(username); ((PoolXADataSource) pds).setPassword(password); ((PoolXADataSource) pds).setMinPoolSize(Integer.valueOf(minPoolSize)); ((PoolXADataSource) pds).setInitialPoolSize(10); ((PoolXADataSource) pds).setMaxPoolSize(Integer.valueOf(maxPoolSize)); ((PoolXADataSource) pds).setDataSourceName(dataSourceName); ((PoolXADataSource) pds).setConnectionPoolName(connectionPoolName); System.out.println("XADataSourceConfig: XADataSource created"); } catch (SQLException ex) { System.err.println("Error connecting to the database: " + ex.getMessage()); } return pds; } // Create an entity manager factory object @Bean(name = "entityManagerFactory") public EntityManagerFactory createEntityManagerFactory() throws SQLException { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); entityManagerFactoryBean.setDataSource(getDataSource()); entityManagerFactoryBean.setPackagesToScan(new String[] { "com.oracle.mtm.sample.entity" }); entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class); entityManagerFactoryBean.setPersistenceUnitName("mydeptxads"); Properties properties = new Properties(); properties.setProperty( "javax.persistence.transactionType", "RESOURCE_LOCAL"); // change this to resource_local properties.put("hibernate.show_sql", "true"); properties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect"); properties.put("hibernate.format_sql", "true"); properties.put("hbm2ddl.auto", "validate"); properties.put("hibernate.connection.provider_class", "oracle.tmm.jta.jpa.hibernate.HibernateXADataSourceConnectionProvider"); entityManagerFactoryBean.setJpaProperties(properties); entityManagerFactoryBean.afterPropertiesSet(); EntityManagerFactory emf = (EntityManagerFactory) entityManagerFactoryBean.getObject(); System.out.println("entityManagerFactory = " + emf); // Pass the entity manager factory object to the MicroTx Library // If you are using a single resource manager with your application, //pass the entity manager factory object to the MicroTx library in the following way. TrmConfig.initEntityManagerFactory(emf); // If you are using multiple resource managers with your application, // pass the entity manager factory object to the MicroTx library in the following way. TrmConfig.initEntityManagerFactory(emf, departmentDataSource, ORCL1-8976-9776-9873); return emf; } }
To initialize the Entity Manager Factory object, pass the required parameters to
TrmConfig.initEntityManagerFactory()
based on whether your application connects to a single resource manager or multiple resource managers.-
When your application connects to a single resource manager, create an entity manager factory object and then pass it to the MicroTx library. In the following sample code,
emf
is the name of the entity manager factory object.TrmConfig.initEntityManagerFactory(emf);
-
When your application connects with multiple resource managers, you must pass the following parameters while calling
TrmConfig.initEntityManagerFactory()
.TrmConfig.initEntityManagerFactory(emf, departmentDataSource, ORCL1-8976-9776-9873);
Where,
emf
is the entity manager factory object that you have created, and then you pass it to the MicroTx library.departmentDataSource
is the name of the data source that you have created in the above sample code before callingTrmConfig.initEntityManagerFactory()
.ORCL1-8976-9776-9873
is the resource manager ID (RMID).
-
- Only for Spring Boot applications that use the JAX-RS API, perform the following tasks after registering the resource endpoint that participates in the XA transaction.
-
Register the filters and
XAResourceCallbacks
for prepare, commit, rollback as shown in the following sample code snippet.@Component public class Configuration extends ResourceConfig { public Configuration() { // Register the MicroTx XA resource callback which // coordinates with the transaction coordinator register(XAResourceCallbacks.class); // Register the filters for the MicroTx libraries that // intercept the JAX_RS calls and manage the XA transactions register(TrmTransactionResponseFilter.class); register(TrmTransactionRequestFilter.class); // Bind the connection ... } }
-
Skip this step if you are using multiple resource managers. If you are using a single resource manager with your Spring Boot application, bind the
TrmEntityManager
object to anEntityManager
. Later, you will use theTrmEntityManager
object in your application code so that MicroTx handles the connection.@Component public class Configuration extends ResourceConfig { public Configuration() { // Register the filters as shown in the previous step .... // Bind the connection register(new AbstractBinder() { @Override protected void configure() { //Bind the TrmEntityManager object to an EntityManager object bindFactory(TrmEntityManagerFactory.class).to(EntityManager.class); } }); } }
- Skip this step if you are using single resource manager. If you are using multiple resource managers,initialize a Bean for each resource manager. The following sample code snippet describes how you can initialize two Beans, one for each resource manager that the application uses. In the following code sample,
departmentDataSource
andcreditDataSource
are names of the XA data source that you have provided in your application's YAML file. Note down the names of the data source as you will use the@Inject
annotation to ensure that the participant application uses these connections.@Component public class Configuration extends ResourceConfig { public Configuration() { // Register the filters as shown in the previous step .... // Initialize a bean for every resource manager that you want to use with your app @Bean @TrmEntityManager(name = "departmentDataSource") @Lazy @RequestScope public EntityManager departmentDSSqlConnectionBean() throws SQLException { return new TrmEntityManagerFactory().getEntityManagerByName("departmentDataSource"); } @Bean @TrmEntityManager(name = "creditDataSource") @Lazy @RequestScope public EntityManager creditDSSqlConnectionBean() throws SQLException { return new TrmConnectionFactory().getConnection("creditDataSource"); } } }
-
- Create a
.java
file in the folder that contains your application code to initialize anPoolXADataSource
object. ThePoolXADataSource
class contains methods to create custom data source and entity manager factory objects.The following example code shows how you can initialize the library in within the
PoolXADataSource
class, create a custom data source nameddepartmentDataSource
, and create an entity manager factory object. You can create similar code for your application.The custom data source object contains details to connect with the resource manager. It is the responsibility of the application developer to ensure that an XA-compliant JDBC driver and required parameters are set up while creating a custom data source object.
package com.oracle.mtm.sample; import oracle.tmm.common.TrmConfig; import oracle.ucp.jdbc.PoolDataSourceFactory; import oracle.ucp.jdbc.PoolXADataSource; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Initialized; import javax.enterprise.event.Observes; import javax.enterprise.inject.Produces; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import java.lang.invoke.MethodHandles; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; @ApplicationScoped public class Configuration { private PoolXADataSource dataSource; final static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private EntityManagerFactory entityManagerFactory; @Inject @ConfigProperty(name = "departmentDataSource.url") String url; @Inject @ConfigProperty(name = "departmentDataSource.user") String user; @Inject @ConfigProperty(name = "departmentDataSource.password") String password; private void init(@Observes @Initialized(ApplicationScoped.class) Object event) { initialiseDataSource(); createEntityManagerFactory(); } /** * Initializes the datasource into the MicroTx library that manages the lifecycle of the XA transaction * */ private void initialiseDataSource() { try { this.dataSource = PoolDataSourceFactory.getPoolXADataSource(); this.dataSource.setURL(url); this.dataSource.setUser(user); this.dataSource.setPassword(password); this.dataSource.setConnectionFactoryClassName("oracle.jdbc.xa.client.OracleXADataSource"); this.dataSource.setMaxPoolSize(15); } catch (SQLException e) { logger.error("Failed to initialise database"); } } public PoolXADataSource getDatasource() { return dataSource; } public void createEntityManagerFactory(){ Map<String, Object> props = new HashMap<String, Object>(); props.put("hibernate.connection.datasource", getDatasource()); props.put("hibernate.show_sql", "true"); props.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect"); props.put("hibernate.hbm2ddl.auto", "none"); props.put("hibernate.format_sql", "true"); props.put("hibernate.connection.provider_class", "oracle.tmm.jta.jpa.hibernate.HibernateXADataSourceConnectionProvider"); props.put("javax.persistence.transactionType", "RESOURCE_LOCAL"); props.put("javax.persistence.jdbc.driver", "oracle.jdbc.OracleDriver"); props.put("javax.persistence.jdbc.url", url); props.put("javax.persistence.jdbc.user", user); props.put("javax.persistence.jdbc.password", password); this.entityManagerFactory = Persistence.createEntityManagerFactory("mydeptxads", props); //Initialize the MicroTx Library // If you are using a single resource manager with your application, //pass the entity manager factory object to the MicroTx library in the following way. TrmConfig.initEntityManagerFactory(this.entityManagerFactory); //If you are using multiple resource managers with your application, //set the setRAC property if you are using an Oracle RAC Database departmentDataSource.setRAC(true); //If you are using multiple resource managers with your application, //pass the entity manager factory object to the MicroTx library in the following way. TrmConfig.initEntityManagerFactory(this.entityManagerFactory, departmentDataSource, ORCL1-8976-9776-9873); } public EntityManagerFactory getEntityManagerFactory() { return this.entityManagerFactory; } public Logger getLogger() { return logger; } /** * EntityManager bean for non-distributed database operations. */ @Produces public EntityManager getEntityManager() { EntityManager entityManager = null; try { entityManager = this.getEntityManagerFactory().createEntityManager(); } catch (RuntimeException e){ e.printStackTrace(); logger.error("Entity manager bean for local transactions creation failed!"); } return entityManager; } }
To initialize the Entity Manager Factory object, pass the required parameters to
TrmConfig.initEntityManagerFactory()
based on whether your application connects to a single resource manager or multiple resource managers.-
When your application connects to a single resource manager, create an entity manager factory object and then pass it to the MicroTx library.
TrmConfig.initEntityManagerFactory(this.entityManagerFactory);
-
When your application connects with multiple resource managers, you must pass the following parameters while calling
TrmConfig.initEntityManagerFactory()
.TrmConfig.initEntityManagerFactory(this.entityManagerFactory, departmentDataSource, ORCL1-8976-9776-9873);
Where,
departmentDataSource
is the name of the data source that you have created in the above sample code before callingTrmConfig.initEntityManagerFactory()
.ORCL1-8976-9776-9873
is the resource manager ID (RMID).
If you are using multiple resource managers, you must set
departmentDataSource.setRAC(true)
for the data source that uses an Oracle RAC Database. -
- Insert the following line in the code of the participant service so that the application uses the connection passed by the MicroTx client library. The following code in the participant application injects the
connection
object that is created by the MicroTx client library.-
If you use a single resource manager with a single application, inject an
EntityManager
object as shown in the following code sample.@Inject @TrmEntityManager private EntityManager emf;
-
When you use multiple resource managers with your application, inject an
EntityManager
object for every resource manager as shown in the following code sample.@Inject @TrmEntityManager(name = "departmentDataSource") private EntityManager emf; @Inject @TrmEntityManager(name = "creditDataSource") private EntityManager emf;
Where,
emf
is the entity manager factory object anddepartmentDataSource
andcreditDataSource
are data source objects that you have created in the previous step. The earlier code sample provides details aboutdepartmentDataSource
. Provide information for other resource managers, such ascreditDataSource
in a similar way.
-
- In your application code, inject the entity manager object that you have passed to the MicroTx library. Use the entity manager object in your application code based on your business logic, and then use this object to connect to the database.
The following example code shows how the entity manager object is injected and used.
@POST @Path("{accountId}/withdraw") public Response withdraw(@PathParam("accountId") String accountId, @QueryParam("amount") double amount, @Context EntityManager entityManager) { // Application code or business logic if(amount == 0){ return Response.status(422,"Amount must be greater than zero").build(); } try { if (this.accountService.getBalance(accountId, entityManager) < amount) { return Response.status(422, "Insufficient balance in the account").build(); } if(this.accountService.withdraw(accountId, amount, entityManager)) { config.getLogger().log(Level.INFO, amount + " withdrawn from account: " + accountId); return Response.ok("Amount withdrawn from the account").build(); } } catch (SQLException | IllegalArgumentException e) { config.getLogger().log(Level.SEVERE, e.getLocalizedMessage()); return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } return Response.serverError().entity("Withdraw failed").build(); }
- Save the changes.
Source code of a sample JAX-RS transaction participant application which uses the MicroTx library and Hibernate as the JPA provider is available in the department-helidon-jpa
folder. Source code of a sample JAX-RS transaction participant application which uses the MicroTx library and EclipseLink as the JPA provider is available in the department-helidon-jpa-eclipselink
folder. Use the source code, which is available in the microtx-samples
GitHub repository, as a reference while integrating the MicroTx libraries with your application.
Parent topic: Configure JPA-Based Java App as Transaction Participant