Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spring's JDBC connection access disables shared cache in EclipseLink [SPR-7753] #12409

Closed
spring-issuemaster opened this issue Nov 18, 2010 · 6 comments

Comments

@spring-issuemaster
Copy link
Collaborator

commented Nov 18, 2010

James Sutherland opened SPR-7753 and commented

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.


Affects: 4.1.1

Issue Links:

  • #12547 EclipseLink does not support read-only database connections
  • #16924 EclipseLinkJpaDialect does not support declarative transaction isolation

Referenced from: commits e4753c9

2 votes, 5 watchers

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Feb 8, 2013

Jorg Heymans commented

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

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 21, 2014

Igor Mukhin commented

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!

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 21, 2014

Juergen Hoeller commented

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

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 21, 2014

Igor Mukhin commented

Thanks, Juergen. Sounds good.

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 22, 2014

Juergen Hoeller commented

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

@spring-issuemaster

This comment has been minimized.

Copy link
Collaborator Author

commented Oct 27, 2014

Igor Mukhin commented

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.