Uploaded image for project: 'Spring Framework'
  1. Spring Framework
  2. SPR-7753

Spring's JDBC connection access disables shared cache in EclipseLink

    Details

    • Type: Improvement
    • Status: Closed
    • Priority: Major
    • Resolution: Complete
    • Affects Version/s: 4.1.1
    • Fix Version/s: 4.1.2
    • Component/s: None
    • Labels:
      None
    • Last commented by a User:
      true

      Description

      In the EclipseLinkJpaDialect Spring does the following,

      @Override
      public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
      throws PersistenceException, SQLException, TransactionException {

      super.beginTransaction(entityManager, definition);
      if (!definition.isReadOnly() && !this.lazyDatabaseTransaction)

      { // This is the magic bit. As with the existing Spring TopLink integration, // begin an early transaction to force EclipseLink to get a JDBC Connection // so that Spring can manage transactions with JDBC as well as EclipseLink. UnitOfWork uow = (UnitOfWork) getSession(entityManager); uow.beginEarlyTransaction(); }

      // Could return the UOW, if there were any advantage in having it later.
      return null;
      }

      This is done to force EclipseLink to read through a transactional connection, so that it can see changes made directly through JDBC. But it has the side-affect of effectively disabling the shared cache in EclipseLink.

      EclipseLink will no longer cache any objects read because a transactional connection is being used.

      Which connection is used is configurable in EclipseLink by the user, so Spring should not be forcing any setting on the user, and not be using an internal API to do it.

      The correct way to enable this is either to set the persistence unit property,
      "eclipselink.jdbc.exclusive-connection.mode"="Always" (will allow a shared cache)

      or
      "eclipselink.transaction.join-existing"="true" (does not allow a shared cache)

      If Spring desires different functionality than the EclipseLink defaults (not sure it should), then it should just default these properties if they have "not" already be configured by the user. This would allow the user to choose if they want caching to work or not.

      Otherwise just remove this code and let the user configure if they wish to allow caching or not.

        Issue Links

          Activity

          Hide
          heymjo Jorg Heymans added a comment -

          From what i can see EclipseLinkJpaDialect only comes into play when using the JpaTransactionManager, when using JTA above mechanism is not active.

          Show
          heymjo Jorg Heymans added a comment - From what i can see EclipseLinkJpaDialect only comes into play when using the JpaTransactionManager, when using JTA above mechanism is not active.
          Hide
          iimuhin Igor Mukhin added a comment - - edited

          I found this workaround to enable shared cache in EclipseLink in Spring environment:

          @Bean
          public EntityManagerFactory entityManagerFactory() {
              LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
              factory.setDataSource(dataSource());
              factory.setPersistenceUnitName("main");
           
              final EclipseLinkJpaDialect customDialect = new EclipseLinkJpaDialect() {
                  @Override
                  public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) throws PersistenceException, SQLException {
                      // Hides: return super.getJdbcConnection(entityManager, readOnly);
                      // IMPORTANT LINE
                      return null;
                  }
              };
           
              // IMPORTANT LINE
              customDialect.setLazyDatabaseTransaction(true);
           
              EclipseLinkJpaVendorAdapter customAdapter = new EclipseLinkJpaVendorAdapter() {
                  @Override
                  public JpaDialect getJpaDialect() {
                      return customDialect;
                  }
              };
           
              customAdapter.setDatabase(Database.ORACLE);
              factory.setJpaVendorAdapter(customAdapter);
           
              factory.afterPropertiesSet();
              return factory.getObject();
          }
          

          The problematic place in Spring is the method JpaTransactionManager.doBegin(...):

          // Register the JPA EntityManager's JDBC Connection for the DataSource, if set.
          if (getDataSource() != null) {
          	ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly());
          

          This method obtains the jdbc connection from EclipseLink, which forces EclipseLink to go to non-shared-cache-mode. BUT it wants obtains the jdbc connection only in case dataSource is set, which is strange!

          Show
          iimuhin Igor Mukhin added a comment - - edited I found this workaround to enable shared cache in EclipseLink in Spring environment: @Bean public EntityManagerFactory entityManagerFactory() { LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource()); factory.setPersistenceUnitName("main");   final EclipseLinkJpaDialect customDialect = new EclipseLinkJpaDialect() { @Override public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) throws PersistenceException, SQLException { // Hides: return super.getJdbcConnection(entityManager, readOnly); // IMPORTANT LINE return null; } };   // IMPORTANT LINE customDialect.setLazyDatabaseTransaction(true);   EclipseLinkJpaVendorAdapter customAdapter = new EclipseLinkJpaVendorAdapter() { @Override public JpaDialect getJpaDialect() { return customDialect; } };   customAdapter.setDatabase(Database.ORACLE); factory.setJpaVendorAdapter(customAdapter);   factory.afterPropertiesSet(); return factory.getObject(); } The problematic place in Spring is the method JpaTransactionManager.doBegin(...): // Register the JPA EntityManager's JDBC Connection for the DataSource, if set. if (getDataSource() != null) { ConnectionHandle conHandle = getJpaDialect().getJdbcConnection(em, definition.isReadOnly()); This method obtains the jdbc connection from EclipseLink, which forces EclipseLink to go to non-shared-cache-mode. BUT it wants obtains the jdbc connection only in case dataSource is set, which is strange!
          Hide
          juergen.hoeller Juergen Hoeller added a comment - - edited

          JpaTransactionManager obtains the JDBC Connection just for exposure to application code, keyed by DataSource - which is why it only calls that method if a DataSource has been set (or derived from the EntityManagerFactory), otherwise it wouldn't have a key to expose it to.

          In any case, point taken that obtaining a JDBC Connection from an EclipseLink EntityManager implicitly enforces an early transaction. I'll switch this into lazy connection retrieval via a ConnectionHandle, analogous to our OpenJPA and Hibernate dialects. You'll still have to do setLazyDatabaseTransaction(true) or declare readOnly=true on all applicable transaction demarcations. It should consistently work then, as long as no other code of yours triggers actual access to a JDBC Connection for the same DataSource.

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - - edited JpaTransactionManager obtains the JDBC Connection just for exposure to application code, keyed by DataSource - which is why it only calls that method if a DataSource has been set (or derived from the EntityManagerFactory ), otherwise it wouldn't have a key to expose it to. In any case, point taken that obtaining a JDBC Connection from an EclipseLink EntityManager implicitly enforces an early transaction. I'll switch this into lazy connection retrieval via a ConnectionHandle , analogous to our OpenJPA and Hibernate dialects. You'll still have to do setLazyDatabaseTransaction(true) or declare readOnly=true on all applicable transaction demarcations. It should consistently work then, as long as no other code of yours triggers actual access to a JDBC Connection for the same DataSource . Juergen
          Hide
          iimuhin Igor Mukhin added a comment -

          Thanks, Juergen. Sounds good.

          Show
          iimuhin Igor Mukhin added a comment - Thanks, Juergen. Sounds good.
          Hide
          juergen.hoeller Juergen Hoeller added a comment -

          This is available in the latest 4.1.2.BUILD-SNAPSHOT (see http://projects.spring.io/spring-framework/) in the meantime. Would be great if you could give it a try...

          Juergen

          Show
          juergen.hoeller Juergen Hoeller added a comment - This is available in the latest 4.1.2.BUILD-SNAPSHOT (see http://projects.spring.io/spring-framework/ ) in the meantime. Would be great if you could give it a try... Juergen
          Hide
          iimuhin Igor Mukhin added a comment -

          Juergen, thanks, it works as you described with:

                  EclipseLinkJpaVendorAdapter adapter = new EclipseLinkJpaVendorAdapter();
                  ((EclipseLinkJpaDialect) adapter.getJpaDialect()).setLazyDatabaseTransaction(true);
                  factory.setJpaVendorAdapter(adapter);
          

          and

              <dependencyManagement>
                  <dependencies>
                      <dependency>
                          <groupId>org.springframework</groupId>
                          <artifactId>spring-orm</artifactId>
                          <version>4.1.2.BUILD-SNAPSHOT</version>
                      </dependency>
                  </dependencies>
              </dependencyManagement>
          

          Show
          iimuhin Igor Mukhin added a comment - Juergen, thanks, it works as you described with: EclipseLinkJpaVendorAdapter adapter = new EclipseLinkJpaVendorAdapter(); ((EclipseLinkJpaDialect) adapter.getJpaDialect()).setLazyDatabaseTransaction(true); factory.setJpaVendorAdapter(adapter); and <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.1.2.BUILD-SNAPSHOT</version> </dependency> </dependencies> </dependencyManagement>

            People

            • Assignee:
              juergen.hoeller Juergen Hoeller
              Reporter:
              jamesssss James Sutherland
              Last updater:
              St├ęphane Nicoll
            • Votes:
              2 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:
                Days since last comment:
                35 weeks, 6 days ago