이 콘텐츠는 선택한 언어로 제공되지 않습니다.
Chapter 3. Interfaces for configuring and referencing transaction managers
JavaEE and Spring Boot each provide a transaction client interface for configuring the transaction manager in Fuse and for using the transaction manager in deployed applications. There is a clear distinction between configuration, which is an administrative task, and referencing, which is a development task. The application developer is responsible for pointing the application to a previously configured transaction manager.
- Section 3.1, “What transaction managers do”
- Section 3.2, “About local, global, and distributed transaction managers”
- Section 3.3, “Using a JavaEE transaction client”
- Section 3.4, “Using a Spring Boot transaction client”
- Section 3.5, “OSGi interfaces between transaction clients and the transaction manager”
3.1. What transaction managers do
A transaction manager is the part of an application that is responsible for coordinating transactions across one or more resources. The responsibilities of the transaction manager are as follows:
- Demarcation – starting and ending transactions by using begin, commit, and rollback methods.
- Managing the transaction context – a transaction context contains the information that a transaction manager needs to keep track of a transaction. The transaction manager is responsible for creating transaction contexts and attaching them to the current thread.
Coordinating the transaction across multiple resources – enterprise-level transaction managers typically have the capability to coordinate a transaction across multiple resources. This feature requires the 2-phase commit protocol and resources must be registered and managed using the XA protocol. See Section 1.7.1.2, “Support for the XA standard”.
This is an advanced feature that is not supported by all transaction managers.
- Recovery from failure – transaction managers are responsible for ensuring that resources are not left in an inconsistent state if there is a system failure and the application fails. In some cases, manual intervention might be required to restore the system to a consistent state.
3.2. About local, global, and distributed transaction managers
A transaction manager can be local, global, or distributed.
3.2.1. About local transaction managers
A local transaction manager is a transaction manager that can coordinate transactions for only a single resource. The implementation of a local transaction manager is typically embedded in the resource itself and the transaction manager used by application is a thin wrapper around this built-in transaction manager.
For example, the Oracle database has a built-in transaction manager that supports demarcation operations (by using SQL BEGIN
, COMMIT
, or ROLLBACK
statements or by using a native Oracle API) and various levels of transaction isolation. Control over the Oracle transaction manager can be exported through JDBC, and this JDBC API is used by applications to demarcate transactions.
It is important to understand what constitutes a resource, in this context. For example, if you are using a JMS product, the JMS resource is the single running instance of the JMS product, not the individual queues and topics. Moreover, sometimes, what appears to be multiple resources might actually be a single resource, if the same underlying resource is accessed in different ways. For example, your application might access a relational database both directly (through JDBC) and indirectly (through an object-relational mapping tool like Hibernate). In this case, the same underlying transaction manager is involved, so it should be possible to enroll both of these code fragments in the same transaction.
It cannot be guaranteed that this will work in every case. Although it is possible in principle, some detail in the design of the Spring Framework or other wrapper layers might prevent it from working in practice.
It is possible for an application to have many different local transaction managers working independently of each other. For example, you could have one Camel route that manipulates JMS queues and topics, where the JMS endpoints reference a JMS transaction manager. Another route could access a relational database through JDBC. But you could not combine JDBC and JMS access in the same route and have them both participate in the same transaction.
3.2.2. About global transaction managers
A global transaction manager is a transaction manager that can coordinate transactions over multiple resources. This is required when you cannot rely on the transaction manager built into the resource itself. An external system, sometimes called a transaction processing monitor (TP monitor), is capable of coordinating transactions across different resources.
The following are the prerequisites for transactions that operate on multiple resources:
- Global transaction manager or TP monitor – an external transaction system that implements the 2-phase commit protocol for coordinating multiple XA resources.
- Resources that support the XA standard – to participate in a 2-phase commit, resources must support the XA standard. See Section 1.7.1.2, “Support for the XA standard”. In practice, this means that the resource is capable of exporting an XA switch object, which gives complete control of transactions to the external TP monitor.
The Spring Framework does not by itself provide a TP monitor to manage global transactions. It does, however, provide support for integrating with an OSGi-provided TP monitor or with a JavaEE-provided TP monitor (where the integration is implemented by the JtaTransactionManager class). Hence, if you deploy your application into an OSGi container with full transaction support, you can use multiple transactional resources in Spring.
3.2.3. About distributed transaction managers
Usually, a server connects directly to the resources involved in a transaction. In a distributed system, however, it is occasionally necessary to connect to resources that are exposed only indirectly, through a Web service. In this case, you require a TP monitor that is capable of supporting distributed transactions. Several standards are available that describe how to support transactions for various distributed protocols, for example, the WS-AtomicTransactions specification for Web services.
3.3. Using a JavaEE transaction client
When using JavaEE, the most fundamantal and standard method to interact with a transaction manager is the Java Transaction API (JTA) interface, javax.transaction.UserTransaction
. The canonical usage is:
InitialContext context = new InitialContext(); UserTransaction ut = (UserTransaction) context.lookup("java:comp/UserTransaction"); ut.begin(); // Access transactional, JTA-aware resources such as database and/or message broker ut.commit(); // or ut.rollback()
Obtaining a UserTransaction
instance from JNDI (Java Naming and Directory Interface) is one way of getting a transaction client. In a JavaEE environment, you can access a transaction client, for example, with CDI (context and dependency injection).
The following figure shows a typica JavaEE Camel application.
The figure shows that both Camel code and application code may access:
-
A
javax.transaction.UserTransaction
instance to demarcate transactions either directly from an application or internally through transaction-aware Camel components by using the SpringTransactionTemplate
class. -
Databases through JDBC APIs either directly or, for example, by using Spring’s
JdbcTemplate
, or by using thecamel-jdbc
component. -
Message brokers through a JMS API either directly, by using Spring’s
JmsTemplate
class or by using thecamel-jms
component.
When using a javax.transaction.UserTransaction
object, you do not need to be aware of the actual transaction manager that is being used because you are working directly with only the transaction client. (See Section 1.3, “About transaction clients”.) A different approach is taken by Spring and Camel, as it uses Spring’s transaction facilities internally.
JavaEE Application
In typical JavaEE scenario, the application is deployed to a JavaEE application server, usually as a WAR
or EAR
archive. By means of JNDI or CDI, the application may access an instance of the javax.transaction.UserTransaction
service. The aplication then uses this transaction client instance to demarcate transactions. Within a transaction, the application performs JDBC and/or JMS access.
Camel component and application code
These represent the code that performs JMS/JDBC operations. Camel has its own advanced methods to access JMS/JDBC resources. The application code may use a given API directly.
JMS Connection Factory
This is the javax.jms.ConnectionFactory
interface that is used to obtain instances of javax.jms.Connection
and then javax.jms.Session
(or javax.jms.JmsContext
in JMS 2.0). This may be used directly by the application or indirectly in Camel components, which may use org.springframework.jms.core.JmsTemplate
internally. Neither application code nor a Camel component require the details of this connection factory. The connection factory is configured at the application server. You can see this configuration in a JavaEE server. An OSGi server such as Fuse is similar. A system administrator configures the connection factory independently of the application. Typically, the connection factory implements pooling capabilities.
JDBC Data Source
This is the javax.sql.DataSource
interface that is used to obtain instances of java.sql.Connection
. As with JMS, this data source may be used directly or indirectly. For example, the camel-sql
component uses the org.springframework.jdbc.core.JdbcTemplate
class internally. As with JMS, neither application code nor Camel require the details of this data source. The configuration is done inside the application server or inside the OSGi server by using methods that are described in Chapter 4, Configuring the Narayana transaction manager.
3.4. Using a Spring Boot transaction client
One of the main goals of the Spring Framework (and Spring Boot) is to make JavaEE APIs easier to use. All major JavaEE vanilla APIs have their part in the Spring Framework (Spring Boot). These are not alternatives or replacements of given APIs, but rather wrappers that add more configuration options or more consistent usage, for example, with respect to exception handling.
The following table matches a given JavaEE API with its Spring-related interface:
JavaEE API | Spring Utility | Configured With |
---|---|---|
JDBC |
|
|
JMS |
|
|
JTA |
|
|
JdbcTemplate
and JmsTemplate
directly use javax.sql.DataSource
and javax.jms.ConnectionFactory
respectively. But TransactionTemplate
uses the Spring interface of PlatformTransactionManager
. This is where Spring does not simply improve JavaEE, but replaces the JavaEE client API with its own.
Spring treats javax.transaction.UserTransaction
as an interface that is too simple for real-world scenarios. Also, because javax.transaction.UserTransaction
does not distinguish between local, single resource transactions and global, multi-resource transactions, implementations of org.springframework.transaction.PlatformTransactionManager
give developers more freedom.
Following is the canonical API usage of Spring Boot:
// Create or get from ApplicationContext or injected with @Inject/@Autowired. JmsTemplate jms = new JmsTemplate(...); JdbcTemplate jdbc = new JdbcTemplate(...); TransactionTemplate tx = new TransactionTemplate(...); tx.execute((status) -> { // Perform JMS operations within transaction. jms.execute((SessionCallback<Object>)(session) -> { // Perform operations on JMS session return ...; }); // Perform JDBC operations within transaction. jdbc.execute((ConnectionCallback<Object>)(connection) -> { // Perform operations on JDBC connection. return ...; }); return ...; });
In the above example, all three kinds of templates are simply instantiated, but they may also be obtained from Spring’s ApplicationContext
, or injected by using @Autowired
annotations.
3.4.1. Using the Spring PlatformTransactionManager interface
As mentioned earlier, javax.transaction.UserTransaction
is usually obtained from JNDI in a JavaEE application. But Spring provides explicit implementations of this interface for many scenarios. You do not always need full JTA scenarios and sometimes an application requires access to just a single resource, for example, JDBC.
Usually, org.springframework.transaction.PlatformTransactionManager
is the Spring transaction client API that provides the classic transaction client operations: begin
, commit
and rollback
. In other words, this interface provides the essential methods for controlling transactions at runtime.
The other key aspect of any transaction system is the API for implementing transactional resources. But transactional resources are usually implemented by the underlying database, so this aspect of transactional programming is rarely a concern for the application programmer.
3.4.1.1. Definition of the PlatformTransactionManager interface
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
3.4.1.2. About the TransactionDefinition interface
You use the TransactionDefinition
interface to specify the characteristics of a newly created transaction. You can specify the isolation level and the propagation policy of the new transaction. For details, see Section 9.4, “Transaction propagation policies”.
3.4.1.3. Definition of the TransactionStatus interface
You can use the TransactionStatus
interface to check the status of the current transaction, that is, the transaction that is associated with the current thread, and to mark the current transaction for rollback. This is the interface definition:
public interface TransactionStatus extends SavepointManager, Flushable { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
3.4.1.4. Methods defined by the PlatformTransactionManager interface
The PlatformTransactionManager
interface defines the following methods:
- getTransaction()
-
Creates a new transaction and associates it with the current thread by passing in a
TransactionDefinition
object that defines the characteristics of the new transaction. This is analogous to thebegin()
method of many other transaction client APIs. - commit()
- Commits the current transaction, which makes all of the pending changes to the registered resources permanent.
- rollback()
- Rolls back the current transaction, which undoes all pending changes to the registered resources.
3.4.2. Steps for using the transaction manager
Usually, you do not use the PlatformTransactionManager
interface directly. In Apache Camel, you typically use a transaction manager as follows:
- Create an instance of a transaction manager. There are several different implementations available in Spring, see Section 3.4, “Using a Spring Boot transaction client”).
-
Pass the transaction manager instance to either an Apache Camel component or to the
transacted()
DSL command in a route. The transactional component or thetransacted()
command is then responsible for demarcating transactions. For details, see Chapter 9, Writing a Camel application that uses transactions).
3.4.3. About Spring PlatformTransactionManager implementations
This section provides a brief overview of the transaction manager implementations that are provided by the Spring Framework. The implementations fall into two categories: local transaction managers and global transaction managers.
Starting from Camel:
-
The
org.apache.camel.component.jms.JmsConfiguration
object that is used by thecamel-jms
component requires an instance of theorg.springframework.transaction.PlatformTransactionManager
interface. -
The
org.apache.camel.component.sql.SqlComponent
uses theorg.springframework.jdbc.core.JdbcTemplate
class internally and this JDBC template also integrates withorg.springframework.transaction.PlatformTransactionManager
.
As you can see, you must have some implementation of this interface. Depending on the scenario, you can configure the required platform transaction manager.
3.4.3.1. Local PlatformTransactionManager implementations
The list below summarizes the local transaction manager implementations that are provided by the Spring Framework. These transaction managers support only a single resource.
- org.springframework.jms.connection.JmsTransactionManager
- This transaction manager implementation is capable of managing a single JMS resource. You can connect to any number of queues or topics, but only if they belong to the same underlying JMS messaging product instance. Moreover, you cannot enlist any other type of resource in a transaction.
- org.springframework.jdbc.datasource.DataSourceTransactionManager
- This transaction manager implementation is capable of managing a single JDBC database resource. You can update any number of different database tables, but only if they belong to the same underlying database instance.
- org.springframework.orm.jpa.JpaTransactionManager
- This transaction manager implementation is capable of managing a Java Persistence API (JPA) resource. It is not possible, however, to simultaneously enlist any other kind of resource in a transaction.
- org.springframework.orm.hibernate5.HibernateTransactionManager
- This transaction manager implementation is capable of managing a Hibernate resource. It is not possible, however, to simultaneously enlist any other kind of resource in a transaction. Moreover, the JPA API is preferred over the native Hibernate API.
There are also other, less frequently used, implementations of PlatformTransactionManager
.
3.4.3.2. Global PlatformTransactionManager implementation
The Spring Framework provides one global transaction manager implementation for use in the OSGi runtime. The org.springframework.transaction.jta.JtaTransactionManager
supports operations on multiple resources in a transaction. This transaction manager supports the XA transaction API and can enlist more than one resource in a transaction. To use this transaction manager, you must deploy your application inside either an OSGi container or a JavaEE server.
While single-resource implementations of PlatformTransactionManager
are actual implementations, JtaTransactionManager
is more of a wrapper for an actual implementation of the standard javax.transaction.TransactionManager
.
This is why it is better to use the JtaTransactionManager
implementation of PlatformTransactionManager
in an environment where you can access (by means of JNDI or CDI) an already configured instance of javax.transaction.TransactionManager
and usually also javax.transaction.UserTransaction
. Usually, both these JTA interfaces are implemented by a single object/service.
Here is an example of configuring/using JtaTransactionManager
:
InitialContext context = new InitialContext(); UserTransaction ut = (UserTransaction) context.lookup("java:comp/UserTransaction"); TransactionManager tm = (TransactionManager) context.lookup("java:/TransactionManager"); JtaTransactionManager jta = new JtaTransactionManager(); jta.setUserTransaction(ut); jta.setTransactionManager(tm); TransactionTemplate jtaTx = new TransactionTemplate(jta); jtaTx.execute((status) -> { // Perform resource access in the context of global transaction. return ...; });
In the above example, the actual instances of JTA objects (UserTransaction
and TransactionManager
) are taken from JNDI. In OSGi, they may as well be obtained from the OSGi service registry.
3.5. OSGi interfaces between transaction clients and the transaction manager
After a description of the JavaEE transaction client API and the Spring Boot transaction client API, it is helpful to see the relationships within an OSGi server, such as Fuse. One of the features of OSGi is the global service registry, which may be used to:
- Look up services by filter or interface(s).
- Register services with given interface(s) and properties.
In the same way that applications that are deployed in a JavaEE application server obtain references to javax.transaction.UserTransaction
by using JNDI (service locator method) or get them injected by CDI (dependency injection method), in OSGi you can obtain the same references (directly or indirectly) in any of the following ways:
-
Invoking the
org.osgi.framework.BundleContext.getServiceReference()
method (service locator). - Get them injected in a Blueprint container.
- Use Service Component Runtime (SCR) annotations (dependency injection).
The following figure shows a Fuse application that is deployed in the OSGi runtime. Application code and/or Camel components use their APIs to obtain references to the transaction manager, data sources, and connection factories.
Applications (bundles) interact with services that are registered in the OSGi registry. The access is performed through interfaces and this is all that should be relevant to applications.
In Fuse, the fundamental object that implements (directly or through a tiny wrapper) transactional client interfaces is org.jboss.narayana.osgi.jta.internal.OsgiTransactionManager
. You can use the following interfaces to access the transaction manager:
-
javax.transaction.TransactionManager
-
javax.transaction.UserTransaction
-
org.springframework.transaction.PlatformTransactionManager
-
org.ops4j.pax.transx.tm.TransactionManager
You can use any of these interfaces directly or you can use them implicitly by choosing a framework or library, such as Camel.
For information about the ways to configure org.jboss.narayana.osgi.jta.internal.OsgiTransactionManager
in Fuse, see Chapter 4, Configuring the Narayana transaction manager. Later chapters in this guide build on the information in that chapter and describe how to configure and use other services, such as JDBC data sources and JMS connection factories.