- Transaction Manager for Microservices開発者ガイド
- XAでのアプリケーションの開発
- XAでのJAX-RSアプリケーションの開発
- トランザクション参加側としてのJPAベースのJavaアプリケーションの構成
- XA準拠リソース・マネージャを使用するJPAベースのJavaアプリケーションの構成
7.11.4.1 XA準拠リソース・マネージャを使用するJPAベースのJavaアプリケーションの構成
XA準拠のリソース・マネージャを使用する場合は、この項に記載されている情報を使用して、XAトランザクションに参加するHelidonまたはSpring BootアプリケーションのJPAプロバイダとしてHibernateまたはEclipseLinkを使用します。
- MicroTxクライアント・ライブラリ・プロパティのプロパティ値を指定します。次の例では、プロパティのサンプル値を指定しています。ご自身の環境に基づいて値を指定してください。 oracle.tmm.TcsConnPoolSize = 15 oracle.tmm.CallbackUrl = https://bookTicket-app:8081 oracle.tmm.PropagateTraceHeaders = true oracle.tmm.TransactionTimeout = 60000 oracle.tmm.xa.XaSupport = trueoracle.tmm.xa.XaSupportがtrueに設定されていることを確認します。各プロパティおよびその他のオプション・プロパティの詳細は、「JAX-RSおよびNode.jsアプリケーションのライブラリ・プロパティの構成」を参照してください。 
- MicroTx Javaライブラリ・ファイルをアプリケーションのpom.xmlファイルにmaven依存関係として含めます。次のサンプル・コードは24.4リリース用です。使用するリリース・バージョンに基づいて、正しいバージョンを指定してください。- 
                              Helidon 2.xなどのJakarta EE8環境では、 TmmLibファイルを使用します。<dependency> <groupId>com.oracle.tmm.jta</groupId> <artifactId>TmmLib</artifactId> <version>24.4</version> </dependency>
- 
                              Helidon 3.xアプリケーションなどのJakarta EE9環境では、 TmmLib-jakartaファイルを使用します。<dependency> <groupId>com.oracle.tmm.jta</groupId> <artifactId>TmmLib-jakarta</artifactId> <version>24.4</version> </dependency>
 
- 
                              
- このタスクは、JAX-RS APIを使用するSpring Bootアプリケーションに対してのみ実行します。アプリケーション・コードを含むフォルダに.javaファイルを作成して、XADataSourceConfigオブジェクトを初期化します。XADataSourceConfigクラスには、カスタム・データ・ソース・オブジェクトおよびエンティティ・マネージャ・ファクトリ・オブジェクトを作成するメソッドが含まれています。次のコード例は、 XADataSourceConfigクラス内のライブラリを初期化し、departmentDataSourceというカスタム・データ・ソースを作成し、emfというエンティティ・マネージャ・ファクトリ・オブジェクトを作成する方法を示しています。ご自身のアプリケーション用に同様のコードを作成できます。カスタム・データ・ソース・オブジェクトには、リソース・マネージャに接続するための詳細が含まれています。アプリケーション開発者は、カスタム・データ・ソース・オブジェクトを作成する際に、XA準拠JDBCドライバおよび必要なパラメータを設定する必要があります。 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; } }エンティティ・マネージャ・ファクトリ・オブジェクトを初期化するには、アプリケーションが1つのリソース・マネージャまたは複数のリソース・マネージャに接続するかに応じて、必要なパラメータを TrmConfig.initEntityManagerFactory()に渡します。- 
                              アプリケーションが1つのリソース・マネージャに接続する場合は、エンティティ・マネージャ・ファクトリ・オブジェクトを作成し、それをMicroTxライブラリに渡します。次のサンプル・コードで、 emfはエンティティ・マネージャ・ファクトリ・オブジェクトの名前です。TrmConfig.initEntityManagerFactory(emf);
- 
                              アプリケーションが複数のリソース・マネージャに接続する場合は、 TrmConfig.initEntityManagerFactory()をコールするときに次のパラメータを渡す必要があります。TrmConfig.initEntityManagerFactory(emf, departmentDataSource, ORCL1-8976-9776-9873);説明 - emfは、作成したエンティティ・マネージャ・ファクトリ・オブジェクトです。これをMicroTxライブラリに渡します。
- departmentDataSourceは、前述のサンプル・コードで- TrmConfig.initEntityManagerFactory()をコールする前に作成したデータ・ソースの名前です。
- ORCL1-8976-9776-9873は、リソース・マネージャID (RMID)です。
 
 
- 
                              
- JAX-RS APIを使用するSpring Bootアプリケーションの場合のみ、XAトランザクションに参加するリソース・エンドポイントを登録した後で次のタスクを実行します。- 
                              次のサンプル・コード・スニペットに示すように、準備、コミット、ロールバックのために、フィルタと XAResourceCallbacksを登録します。@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 ... } }
- 
                              複数のリソース・マネージャを使用している場合は、このステップをスキップします。Spring Bootアプリケーションで1つのリソース・マネージャを使用している場合は、 TrmEntityManagerオブジェクトをEntityManagerにバインドします。後で、TrmEntityManagerオブジェクトをアプリケーション・コードで使用します。これによってMicroTxが接続を処理します。@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); } }); } }
- 1つのリソース・マネージャを使用している場合は、このステップをスキップします。複数のリソース・マネージャを使用している場合は、リソース・マネージャごとにBeanを初期化します。次のサンプル・コード・スニペットでは、2つのBean (アプリケーションが使用するリソース・マネージャごとに1つ)を初期化する方法を示しています。次のコード・サンプルで、departmentDataSourceおよびcreditDataSourceは、アプリケーションのYAMLファイルで指定したXAデータ・ソースの名前です。後で@Inject注釈を使用して参加側アプリケーションでこれらの接続が使用されることを確認するため、データ・ソースの名前を書き留めておきます。@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"); } } }
 
- 
                              
- アプリケーション・コードを含むフォルダに.javaファイルを作成して、PoolXADataSourceオブジェクトを初期化します。PoolXADataSourceクラスには、カスタム・データ・ソース・オブジェクトおよびエンティティ・マネージャ・ファクトリ・オブジェクトを作成するメソッドが含まれています。次のコード例は、 PoolXADataSourceクラス内のライブラリを初期化し、departmentDataSourceというカスタム・データ・ソースを作成し、エンティティ・マネージャ・ファクトリ・オブジェクトを作成する方法を示しています。ご自身のアプリケーションで同様のコードを作成できます。カスタム・データ・ソース・オブジェクトには、リソース・マネージャに接続するための詳細が含まれています。アプリケーション開発者は、カスタム・データ・ソース・オブジェクトを作成する際に、XA準拠JDBCドライバおよび必要なパラメータを設定する必要があります。 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; } }エンティティ・マネージャ・ファクトリ・オブジェクトを初期化するには、アプリケーションが1つのリソース・マネージャまたは複数のリソース・マネージャに接続するかに応じて、必要なパラメータを TrmConfig.initEntityManagerFactory()に渡します。- 
                              アプリケーションが1つのリソース・マネージャに接続する場合は、エンティティ・マネージャ・ファクトリ・オブジェクトを作成し、それをMicroTxライブラリに渡します。 TrmConfig.initEntityManagerFactory(this.entityManagerFactory);
- 
                              アプリケーションが複数のリソース・マネージャに接続する場合は、 TrmConfig.initEntityManagerFactory()をコールするときに次のパラメータを渡す必要があります。TrmConfig.initEntityManagerFactory(this.entityManagerFactory, departmentDataSource, ORCL1-8976-9776-9873);説明 - departmentDataSourceは、前述のサンプル・コードで- TrmConfig.initEntityManagerFactory()をコールする前に作成したデータ・ソースの名前です。
- ORCL1-8976-9776-9873は、リソース・マネージャID (RMID)です。
 
 複数のリソース・マネージャを使用している場合は、Oracle RACデータベースを使用するデータ・ソースに departmentDataSource.setRAC(true)を設定する必要があります。
- 
                              
- 参加側サービスのコードに次の行を挿入して、アプリケーションがMicroTxクライアント・ライブラリによって渡された接続を使用するようにします。参加側アプリケーションの次のコードは、MicroTxクライアント・ライブラリによって作成されたconnectionオブジェクトを注入します。- 
                              1つのアプリケーションで1つのリソース・マネージャを使用する場合は、次のコード・サンプルに示すように EntityManagerオブジェクトを注入します。@Inject @TrmEntityManager private EntityManager emf;
- 
                              アプリケーションで複数のリソース・マネージャを使用する場合は、次のコード・サンプルに示すようにリソース・マネージャごとに EntityManagerオブジェクトを注入します。@Inject @TrmEntityManager(name = "departmentDataSource") private EntityManager emf; @Inject @TrmEntityManager(name = "creditDataSource") private EntityManager emf;ここで、 emfはエンティティ・マネージャ・ファクトリ・オブジェクト、departmentDataSourceおよびcreditDataSourceは前のステップで作成したデータ・ソース・オブジェクトです。前のコード・サンプルは、departmentDataSourceの詳細を示しています。同様の方法で、creditDataSourceなどの他のリソース・マネージャの情報を指定します。
 
- 
                              
- アプリケーション・コードで、MicroTxライブラリに渡したエンティティ・マネージャ・オブジェクトを注入します。ビジネス・ロジックに基づいてアプリケーション・コード内でエンティティ・マネージャ・オブジェクトを使用し、このオブジェクトを使用してデータベースに接続します。次のコード例は、エンティティ・マネージャ・オブジェクトの注入および使用の方法を示しています @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(); }
- 変更内容を保存します。
MicroTxライブラリおよびHibernateをJPAプロバイダとして使用するサンプルJAX-RSトランザクション参加側アプリケーションのソース・コードは、department-helidon-jpaフォルダにあります。MicroTxライブラリおよびEclipseLinkをJPAプロバイダとして使用するサンプルJAX-RSトランザクション参加側アプリケーションのソース・コードは、department-helidon-jpa-eclipselinkフォルダにあります。MicroTxライブラリをアプリケーションと統合する際の参照として、microtx-samples GitHubリポジトリにあるソース・コードを使用します。