6.9.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.

Your application can connect to multiple XA-compliant resource managers. If you are using multiple XA-compliant resource managers for your application, complete the following steps for each resource manager.
  1. 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 to true.

    For details about each property and other optional properties, see Configure Library Properties.

  2. 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.2 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.2</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.2</version>
      </dependency>
  3. 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 an XADataSourceConfig object. The XADataSourceConfig 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 named departmentDataSource, and create an entity manager factory object named emf. 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 calling TrmConfig.initEntityManagerFactory().
      • ORCL1-8976-9776-9873 is the resource manager ID (RMID).
  4. 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.
    1. 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
              ...
          }
      }
    2. 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 an EntityManager. Later, you will use the TrmEntityManager 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);
                  }
              });
          }
      }
    3. 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 and creditDataSource 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");
          }
          }
      }
  5. Create a .java file in the folder that contains your application code to initialize an PoolXADataSource object. The PoolXADataSource 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 named departmentDataSource, 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 calling TrmConfig.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.

  6. 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 and departmentDataSource and creditDataSource are data source objects that you have created in the previous step. The earlier code sample provides details about departmentDataSource. Provide information for other resource managers, such as creditDataSource in a similar way.

  7. 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();
        }
  8. Save the changes.
If there are multiple transaction participant services, then complete these steps for all the participant services.