Uploaded image for project: 'Grails'
  1. Grails
  2. GRAILS-11104

Use BE1PC pattern by default for dealing with transactions spanning multiple datasources

    Details

    • Type: New Feature New Feature
    • Status: Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.3.5
    • Fix Version/s: 2.3.6
    • Component/s: None
    • Labels:
      None

      Description

      Currently you have to use the Atomikos plugin in Grails for dealing with transactions that span multiple datasources.

      However there is a more simpler approach available that's called the "best effort one phase commit" pattern.
      http://www.javaworld.com/article/2077963/open-source-tools/distributed-transactions-in-spring--with-and-without-xa.html?page=2

      Best Efforts 1PC pattern

      The Best Efforts 1PC pattern is fairly general but can fail in some circumstances that the developer must be aware of. This is a non-XA pattern that involves a synchronized single-phase commit of a number of resources. Because the 2PC is not used, it can never be as safe as an XA transaction, but is often good enough if the participants are aware of the compromises. Many high-volume, high-throughput transaction-processing systems are set up this way to improve performance.

      The basic idea is to delay the commit of all resources as late as possible in a transaction so that the only thing that can go wrong is an infrastructure failure (not a business-processing error). Systems that rely on Best Efforts 1PC reason that infrastructure failures are rare enough that they can afford to take the risk in return for higher throughput. If business-processing services are also designed to be idempotent, then little can go wrong in practice.

      Grails should use the BE1PC pattern OOTB when multiple datasources are used. More advanced users can continue to use Atomikos plugin for real XA/2PC (two phase commit) support.

      spring-data-commons contains a BE1PC implementation:
      https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/transaction/ChainedTransactionManager.java
      https://jira.springsource.org/browse/DATACMNS-310

        Issue Links

          Activity

          Hide
          Lari Hotari added a comment -

          Before this change all additional datasources never took part in transactions. They were basically in auto commit mode. Because this is a major change in behaviour and some applications might be dependent on the auto-commit behaviour, it's possible to disable the new transaction manager 'chaining' and usage of the BE1PC pattern.

          It's possible to disable the new behaviour by adding this line to Config.groovy:

          grails.transaction.chainedTransactionManagerPostProcessor.blacklistPattern = '.*'
          

          You can also disable enlisting the transaction manager of an additional datasource by setting 'transactional = false' in the configuration block of the datasource in DataSource.groovy .

          The BE1PC is not a XA/2PC implementation, it doesn't provide safe distributed atomic transactions. If you need XA, look at the Atomikos plugin.

          Show
          Lari Hotari added a comment - Before this change all additional datasources never took part in transactions. They were basically in auto commit mode. Because this is a major change in behaviour and some applications might be dependent on the auto-commit behaviour, it's possible to disable the new transaction manager 'chaining' and usage of the BE1PC pattern. It's possible to disable the new behaviour by adding this line to Config.groovy: grails.transaction.chainedTransactionManagerPostProcessor.blacklistPattern = '.*' You can also disable enlisting the transaction manager of an additional datasource by setting 'transactional = false' in the configuration block of the datasource in DataSource.groovy . The BE1PC is not a XA/2PC implementation, it doesn't provide safe distributed atomic transactions. If you need XA, look at the Atomikos plugin.
          Hide
          zyro added a comment -

          the initialization logic is implemented in a BeanFactoryPostProcessor which calls beanFactory.getBean(...) at some point.

          this can lead to undesired behavior (likely edge-cases), if application code relies on all BeanFactoryPostProcessors being applied.

          e.g. given a plugin with an arbitrary service "MyService" and a plugin descriptor like this:

          class SomePlugin {
          
              def doWithSpring = {
              		
                  myListener(MyListener) {
                      myService = ref "myService"
                  }
          
                  hibernateEventListeners(HibernateEventListeners) {
                      listenerMap = [
                          "pre-insert": myListener,
                      ]
                  }
          
              }
          
          }
          

          running the application will fail with an exception like:

          org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory';
          nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Cannot resolve reference to bean 'hibernateEventListeners' while setting bean property 'hibernateEventListeners';
          nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hibernateEventListeners': Cannot resolve reference to bean 'myListener' while setting bean property 'listenerMap' with key [pre-insert];
          nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myListener': Cannot resolve reference to bean 'myService' while setting bean property 'myService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myService' is defined
          

          the cause is that the ChainedTransactionManagerPostProcessor is applied before the ServiceBeanAliasPostProcessor (ChainedTransactionManagerPostProcessor implements Ordered with HIGHEST_PRECEDENCE). this means while spring tries to initialize myListener, the "myService" alias is not yet available.

          of course, one possible solution for this case is just to inject myService into myListener with its original name "somePluginMyService". but the general issue is not limited to this one scenario, i guess.

          i think it is at least worth being mentioned in the docs that ChainedTransactionManagerPostProcessor

          • runs as first BeanFactoryPostProcessor (at least if there is no other BFPP with HIGHEST_PRECEDENCE)
          • causes eager initialization of the transactionManager/sessionFactory and all injected dependencies (including the hibernateEventListeners and its listener beans)

          zyro

          Show
          zyro added a comment - the initialization logic is implemented in a BeanFactoryPostProcessor which calls beanFactory.getBean(...) at some point. this can lead to undesired behavior (likely edge-cases), if application code relies on all BeanFactoryPostProcessors being applied. e.g. given a plugin with an arbitrary service "MyService" and a plugin descriptor like this: class SomePlugin { def doWithSpring = { myListener(MyListener) { myService = ref "myService" } hibernateEventListeners(HibernateEventListeners) { listenerMap = [ "pre-insert" : myListener, ] } } } running the application will fail with an exception like: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'transactionManager': Cannot resolve reference to bean 'sessionFactory' while setting bean property 'sessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sessionFactory': Cannot resolve reference to bean 'hibernateEventListeners' while setting bean property 'hibernateEventListeners'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'hibernateEventListeners': Cannot resolve reference to bean 'myListener' while setting bean property 'listenerMap' with key [pre-insert]; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myListener': Cannot resolve reference to bean 'myService' while setting bean property 'myService'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myService' is defined the cause is that the ChainedTransactionManagerPostProcessor is applied before the ServiceBeanAliasPostProcessor (ChainedTransactionManagerPostProcessor implements Ordered with HIGHEST_PRECEDENCE). this means while spring tries to initialize myListener, the "myService" alias is not yet available. of course, one possible solution for this case is just to inject myService into myListener with its original name "somePluginMyService". but the general issue is not limited to this one scenario, i guess. i think it is at least worth being mentioned in the docs that ChainedTransactionManagerPostProcessor runs as first BeanFactoryPostProcessor (at least if there is no other BFPP with HIGHEST_PRECEDENCE) causes eager initialization of the transactionManager/sessionFactory and all injected dependencies (including the hibernateEventListeners and its listener beans) zyro
          Hide
          Lari Hotari added a comment -

          @zyro , thanks for your feedback. Could you add a separate jira for the issues you faced? A test app would help fixing the problems.

          Show
          Lari Hotari added a comment - @zyro , thanks for your feedback. Could you add a separate jira for the issues you faced? A test app would help fixing the problems.

            People

            • Assignee:
              Lari Hotari
              Reporter:
              Lari Hotari
            • Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development