Class JmsMatsTransactionManager_JmsAndSpringManagedSqlTx

java.lang.Object
io.mats3.impl.jms.JmsMatsTransactionManager_Jms
io.mats3.spring.jms.tx.JmsMatsTransactionManager_JmsAndSpringManagedSqlTx
All Implemented Interfaces:
JmsMatsStatics, JmsMatsTransactionManager

public class JmsMatsTransactionManager_JmsAndSpringManagedSqlTx extends JmsMatsTransactionManager_Jms
Implementation of JmsMatsTransactionManager that in addition to the JMS transaction keeps a Spring PlatformTransactionManager employing a JDBC DataSource for which it keeps transaction demarcation along with the JMS transaction, by means of "Best Effort 1 Phase Commit". Note that you can choose between providing a DataSource, in which case this JmsMatsTransactionManager internally creates a DataSourceTransactionManager, or you can provide a PlatformTransactionManager employing a DataSource that you've created and employ externally (i.e. DataSourceTransactionManager, JpaTransactionManager or HibernateTransactionManager). In the case where you provide a PlatformTransactionManager, you should definitely wrap the DataSource employed when creating it by the method wrapLazyConnectionDatasource(DataSource), so that you get lazy Connection fetching and so that Mats can know whether the SQL Connection was actually employed within a Mats Stage - this also elides the committing of an empty DB transaction if a Mats Stage does not actually employ a SQL Connection. Note that whether you use the wrapped DataSource or the non-wrapped DataSource when creating e.g. JdbcTemplates does not matter, as Spring's DataSourceUtils and TransactionSynchronizationManager has an unwrapping strategy when retrieving the transactionally demarcated Connection.

Explanation of Best Effort 1 Phase Commit:

  1. JMS transaction is entered (a transactional JMS Connection is always within a transaction)
  2. JMS Message is retrieved.
  3. SQL transaction is entered
  4. Code is executed, including SQL statements and production of new "outgoing" JMS Messages.
  5. SQL transaction is committed - Any errors also rollbacks the JMS Transaction, so that none of them have happened.
  6. JMS transaction is committed.
Out of that order, one can see that if SQL transaction becomes committed, and then the JMS transaction fails, this will be a pretty bad situation. However, of those two transactions, the SQL transaction is absolutely most likely to fail, as this is where you can have business logic failures, concurrency problems (e.g. MS SQL's "Deadlock Victim"), integrity constraints failing etc - that is, failures in both logic and timing. On the other hand, the JMS transaction (which effectively boils down to "yes, I received this message, and sent these") is much harder to fail, where the only situation where it can fail is due to infrastructure/hardware failures (exploding server / full disk on Message Broker). This is called "Best Effort 1PC", and is nicely explained in this article. If this failure occurs, it will be caught and logged on ERROR level (by JmsMatsTransactionManager_Jms) - and then the Message Broker will probably try to redeliver the message. Also read the Should I use XA Transactions from Apache Active MQ.

Wise tip when working with Message Oriented Middleware: Code idempotent! Handle double-deliveries!

The transactionally demarcated SQL Connection can be retrieved from within Mats Stage lambda code user code using ProcessContext.getAttribute(Connection.class) - which also is available using ContextLocal.getAttribute(Connection.class). Notice: In a Spring context, you can also get the transactionally demarcated thread-bound Connection via DataSourceUtils.getConnection(dataSource) - this is indeed what Spring's JDBC Template and friends are doing. If you go directly to the DataSource, you will get a new Connection not participating in the transaction! This "feature" might sometimes be of interest if you want something to be performed regardless of whether the stage processing fails or not. (However, if you do such a thing, you must remember the built-in retry mechanism JMS Message Brokers has: If something fails, whatever database changes you performed successfully with such a non-tx-managed Connection will not participate in the rollback, and will already have been performed when the message is retried. This might, or might not, be what you want.).

  • Field Details

    • CONTEXT_LOCAL_KEY_CONNECTION_EMPLOYED_STATE_SUPPLIER

      public static final String CONTEXT_LOCAL_KEY_CONNECTION_EMPLOYED_STATE_SUPPLIER
      A Supplier<Boolean> bound to MatsFactory.ContextLocal when inside a Mats-transactional demarcation.
      See Also:
  • Method Details

    • create

      Simplest, recommended if you do not need the PlatformTransactionManager in your Spring context! - However, if you need the PlatformTransaction manager in the Spring context, then make it externally (typically using a @Bean annotated method, and make sure to wrap the contained DataSource first with wrapLazyConnectionDatasource(DataSource)), and use the factory method create(PlatformTransactionManager) (it will find the DataSource from the PlatformTransactionManager by introspection).

      Creates an internal DataSourceTransactionManager for this created JmsMatsTransactionManager, and ensures that the supplied DataSource is wrapped using the wrapLazyConnectionDatasource(DataSource) method. Also with this way to construct the instance, Mats will know whether the stage or initiation actually performed any SQL data access.

      Uses a default TransactionDefinition Function, which sets the transaction name, sets Isolation Level to TransactionDefinition.ISOLATION_READ_COMMITTED, and sets Propagation Behavior to TransactionDefinition.PROPAGATION_REQUIRES_NEW.

      Parameters:
      dataSource - the DataSource to make a DataSourceTransactionManager from - which will be wrapped using wrapLazyConnectionDatasource(DataSource) if it not already is.
      Returns:
      a new JmsMatsTransactionManager_JmsAndSpringManagedSqlTx.
    • create

      public static JmsMatsTransactionManager_JmsAndSpringManagedSqlTx create(DataSource dataSource, Function<JmsMatsTransactionManager.JmsMatsTxContextKey,org.springframework.transaction.support.DefaultTransactionDefinition> transactionDefinitionFunction)
      Creates an internal DataSourceTransactionManager for this created JmsMatsTransactionManager, and ensures that the supplied DataSource is wrapped using the wrapLazyConnectionDatasource(DataSource) method. Also with this way to construct the instance, Mats will know whether the stage or initiation actually performed any SQL data access.

      Uses the supplied TransactionDefinition Function to define the transactions - consider create(DataSource) if you are OK with the standard.

      Parameters:
      dataSource - the DataSource to make a DataSourceTransactionManager from - which will be wrapped using wrapLazyConnectionDatasource(DataSource) if it not already is.
      transactionDefinitionFunction - a Function which returns a DefaultTransactionDefinition, possibly based on the provided JmsMatsTransactionManager.JmsMatsTxContextKey (e.g. different isolation level for a special endpoint).
      Returns:
      a new JmsMatsTransactionManager_JmsAndSpringManagedSqlTx.
    • create

      public static JmsMatsTransactionManager_JmsAndSpringManagedSqlTx create(org.springframework.transaction.PlatformTransactionManager platformTransactionManager)
      Next simplest, recommended if you also need the PlatformTransactionManager in your Spring context! (otherwise, use the create(DataSource) factory method). Creates an instance of this class from a provided PlatformTransactionManager (of a type which manages a DataSource), where the supplied instance is introspected to find a method getDataSource() from where to get the underlying DataSource. Do note that you should preferably have the DataSource within the PlatformTransactionManager wrapped using the wrapLazyConnectionDatasource(DataSource) method. If not wrapped as such, Mats will not be able to know whether the stage or initiation actually performed data access.

      Uses the standard TransactionDefinition Function, which sets the transaction name, sets Isolation Level to TransactionDefinition.ISOLATION_READ_COMMITTED (unless HibernateTxMgr), and sets Propagation Behavior to TransactionDefinition.PROPAGATION_REQUIRES_NEW - see getStandardTransactionDefinitionFunctionFor(Class).

      Parameters:
      platformTransactionManager - the DataSourceTransactionManager to use for transaction management.
      Returns:
      a new JmsMatsTransactionManager_JmsAndSpringManagedSqlTx.
    • create

      public static JmsMatsTransactionManager_JmsAndSpringManagedSqlTx create(org.springframework.transaction.PlatformTransactionManager platformTransactionManager, Function<JmsMatsTransactionManager.JmsMatsTxContextKey,org.springframework.transaction.support.DefaultTransactionDefinition> transactionDefinitionFunction)
      Creates an instance of this class from a provided PlatformTransactionManager (of a type which manages a DataSource), where the supplied instance is introspected to find a method getDataSource() from where to get the underlying DataSource. Do note that you should preferably have the DataSource within the PlatformTransactionManager wrapped using the wrapLazyConnectionDatasource(DataSource) method. If not wrapped as such, Mats will not be able to know whether the stage or initiation actually performed data access.

      Uses the supplied TransactionDefinition Function to define the transactions - consider create(PlatformTransactionManager) if you are OK with the standard.

      Parameters:
      platformTransactionManager - the DataSourceTransactionManager to use for transaction management.
      Returns:
      a new JmsMatsTransactionManager_JmsAndSpringManagedSqlTx.
    • create

      public static JmsMatsTransactionManager_JmsAndSpringManagedSqlTx create(org.springframework.transaction.PlatformTransactionManager platformTransactionManager, DataSource dataSource)
      Creates a JmsMatsTransactionManager_JmsAndSpringManagedSqlTx from a provided PlatformTransactionManager (of a type which manages a DataSource) - Do note that you should preferably have the DataSource within the PlatformTransactionManager wrapped using the wrapLazyConnectionDatasource(DataSource) method. If not wrapped as such, Mats will not be able to know whether the stage or initiation actually performed data access.

      Note: Only use this method if the variants NOT taking a DataSource fails to work. It is imperative that the DataSource and the PlatformTransactionManager provided "match up", meaning that the DataSource provided is actually the instance which the PlatformTransactionManager handles.

      Uses the standard TransactionDefinition Function, which sets the transaction name, sets Isolation Level to TransactionDefinition.ISOLATION_READ_COMMITTED (unless HibernateTxMgr), and sets Propagation Behavior to TransactionDefinition.PROPAGATION_REQUIRES_NEW - see getStandardTransactionDefinitionFunctionFor(Class).

      Parameters:
      platformTransactionManager - the DataSourceTransactionManager to use for transaction management.
      dataSource - the DataSource which the supplied PlatformTransactionManager handles.
      Returns:
      a new JmsMatsTransactionManager_JmsAndSpringManagedSqlTx.
    • create

      public static JmsMatsTransactionManager_JmsAndSpringManagedSqlTx create(org.springframework.transaction.PlatformTransactionManager platformTransactionManager, DataSource dataSource, Function<JmsMatsTransactionManager.JmsMatsTxContextKey,org.springframework.transaction.support.DefaultTransactionDefinition> transactionDefinitionFunction)
      Creates a JmsMatsTransactionManager_JmsAndSpringManagedSqlTx from a provided PlatformTransactionManager (of a type which manages a DataSource) - Do note that you should preferably have the DataSource within the PlatformTransactionManager wrapped using the wrapLazyConnectionDatasource(DataSource) method. If not wrapped as such, Mats will not be able to know whether the stage or initiation actually performed data access.

      Note: Only use this method if the variants NOT taking a DataSource fails to work. It is imperative that the DataSource and the PlatformTransactionManager provided "match up", meaning that the DataSource provided is actually the instance which the PlatformTransactionManager handles.

      Uses the supplied TransactionDefinition Function to define the transactions - consider create(PlatformTransactionManager, DataSource) if you are OK with the standard.

      Parameters:
      platformTransactionManager - the PlatformTransactionManager to use for transaction management (must be one employing a DataSource).
      dataSource - the DataSource which the supplied PlatformTransactionManager handles.
      transactionDefinitionFunction - a Function which returns a DefaultTransactionDefinition, possibly based on the provided JmsMatsTransactionManager.JmsMatsTxContextKey (e.g. different isolation level for a special endpoint).
      Returns:
      a new JmsMatsTransactionManager_JmsAndSpringManagedSqlTx.
    • getStandardTransactionDefinitionFunctionFor

      public static Function<JmsMatsTransactionManager.JmsMatsTxContextKey,org.springframework.transaction.support.DefaultTransactionDefinition> getStandardTransactionDefinitionFunctionFor(Class<? extends org.springframework.transaction.PlatformTransactionManager> platformTransactionManager)
      Returns the standard TransactionDefinition Function for the supplied PlatformTransactionManager. Sets Isolation Level to ISOLATION_READ_COMMITTED (unless HibernateTransactionManager, which does not support setting Isolation Level) and Propagation Behavior to PROPAGATION_REQUIRES_NEW - and also sets the name of the transaction to JmsMatsTransactionManager.JmsMatsTxContextKey.toString().
    • wrapLazyConnectionDatasource

      public static DataSource wrapLazyConnectionDatasource(DataSource targetDataSource)
      Creates a proxy/wrapper that has lazy connection getting, and monitoring of whether the connection was actually retrieved. This again enables SpringJmsMats implementation to see if the SQL Connection was actually employed (i.e. a Statement was created) - not only whether we went into transactional demarcation, which a Mats stage always does. This method is internally employed with the DataSource-taking factories of this class which makes an internal DataSourceTransactionManager, but should also be employed if you externally create another type of PlatformTransactionManager, e.g. the HibernateTransactionManager, and provide that to the factories of this class taking a PlatformTransactionManager.

      It returns an instance of DeferredConnectionProxyDataSourceWrapper, but as an extension that also implements Spring's InfrastructureProxy.

      We want this Lazy-and-Monitored DataSource which is returned here to "compare equals" with that of the DataSource which is supplied to us - and which might be employed by other components "on the outside" of Mats - wrt. how Spring's DataSourceUtils compare them in its ThreadLocal cache-hackery. Therefore, the proxy implement InfrastructureProxy (read its JavaDoc!), which means that Spring can trawl its way down to the actual DataSource when it needs to compare. Note: You will find this proxy-handling in the TransactionSynchronizationManager.getResource(Object), which invokes TransactionSynchronizationUtils.unwrapResourceIfNecessary(..), where the instanceof-check for InfraStructureProxy resides.

      "The magnitude of this hack compares favorably with that of the US-of-A's national debt."

      Note: It is not a problem if the DataSource supplied is already wrapped in a LazyConnectionDataSourceProxy, but it is completely unnecessary.

      Tip: If you would want to check/understand how the LazyConnection stuff work, you may within a Mats stage do a DataSourceUtil.getConnection(dataSource) - if the returned Connection's toString() looks like "DeferredConnectionProxy@3ec11999 WITHOUT actual Connection from DataSource@3406472c..." then the actual Connection is still not gotten. When it is gotten (e.g. after having done a SQL CRUD operation), the toString() will look like "DeferredConnectionProxy@3ec11999 WITH actual Connection@b5cc23a"

    • getPlatformTransactionManager

      public org.springframework.transaction.PlatformTransactionManager getPlatformTransactionManager()
      Returns:
      the employed Spring PlatformTransactionManager, which either was created by this instance if this instance was created using one of the DataSource-taking factory methods, or it was provided if this instance was created using one of the PlatformTransactionManager-taking methods.
    • getDataSource

      public DataSource getDataSource()
      Returns:
      the employed DataSource, which either was provided when creating this instance (and thus wrapped), or was reflected out of the provided PlatformTransactionManager. It is hopefully wrapped using the wrapLazyConnectionDatasource(DataSource), which is done automatically if this instance was created using one of the DataSource-taking factory methods. However, if this instance was created using one of the PlatformTransactionManager-taking factory methods, it is up to the user to have wrapped it. Note that it works unwrapped too, but SpringJmsMats cannot then know whether the stages actually employ the SQL Connection, and must do full transaction demarcation around every stage.
    • getDataSourceUnwrapped

      public DataSource getDataSourceUnwrapped()
      Returns:
      the unwrapped variant of getDataSource() - note that it is only any InfrastructureProxys that are unwrapped; Any wrapping done by a database pool is left intact.
    • getTransactionContext

      Description copied from interface: JmsMatsTransactionManager
      Provides an implementation of JmsMatsTransactionManager.TransactionContext. (JMS Connection and Session handling is done by JmsMatsJmsSessionHandler).
      Specified by:
      getTransactionContext in interface JmsMatsTransactionManager
      Overrides:
      getTransactionContext in class JmsMatsTransactionManager_Jms
      Parameters:
      txContextKey - for which JmsMatsStage or JmsMatsInitiator this request for JmsMatsTransactionManager.TransactionContext is for.
      Returns:
      a JmsMatsTransactionManager.TransactionContext for the supplied txContextKey.