001/* 002 * Copyright 2002-2014 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.orm.jpa.vendor; 018 019import java.sql.Connection; 020import java.sql.SQLException; 021import javax.persistence.EntityManager; 022import javax.persistence.PersistenceException; 023 024import org.eclipse.persistence.sessions.UnitOfWork; 025 026import org.springframework.jdbc.datasource.ConnectionHandle; 027import org.springframework.orm.jpa.DefaultJpaDialect; 028import org.springframework.transaction.TransactionDefinition; 029import org.springframework.transaction.TransactionException; 030 031/** 032 * {@link org.springframework.orm.jpa.JpaDialect} implementation for Eclipse 033 * Persistence Services (EclipseLink). Developed and tested against EclipseLink 2.4. 034 * 035 * <p>By default, this class acquires an early EclipseLink transaction with an early 036 * JDBC Connection for non-read-only transactions. This allows for mixing JDBC and 037 * JPA/EclipseLink operations in the same transaction, with cross visibility of 038 * their impact. If this is not needed, set the "lazyDatabaseTransaction" flag to 039 * {@code true} or consistently declare all affected transactions as read-only. 040 * As of Spring 4.1.2, this will reliably avoid early JDBC Connection retrieval 041 * and therefore keep EclipseLink in shared cache mode. 042 * 043 * @author Juergen Hoeller 044 * @since 2.5.2 045 * @see #setLazyDatabaseTransaction 046 * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy 047 */ 048@SuppressWarnings("serial") 049public class EclipseLinkJpaDialect extends DefaultJpaDialect { 050 051 private boolean lazyDatabaseTransaction = false; 052 053 054 /** 055 * Set whether to lazily start a database resource transaction within a 056 * Spring-managed EclipseLink transaction. 057 * <p>By default, read-only transactions are started lazily but regular 058 * non-read-only transactions are started early. This allows for reusing the 059 * same JDBC Connection throughout an entire EclipseLink transaction, for 060 * enforced isolation and consistent visibility with JDBC access code working 061 * on the same DataSource. 062 * <p>Switch this flag to "true" to enforce a lazy database transaction begin 063 * even for non-read-only transactions, allowing access to EclipseLink's 064 * shared cache and following EclipseLink's connection mode configuration, 065 * assuming that isolation and visibility at the JDBC level are less important. 066 * @see org.eclipse.persistence.sessions.UnitOfWork#beginEarlyTransaction() 067 */ 068 public void setLazyDatabaseTransaction(boolean lazyDatabaseTransaction) { 069 this.lazyDatabaseTransaction = lazyDatabaseTransaction; 070 } 071 072 073 @Override 074 public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition) 075 throws PersistenceException, SQLException, TransactionException { 076 077 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { 078 // Pass custom isolation level on to EclipseLink's DatabaseLogin configuration 079 // (since Spring 4.1.2) 080 UnitOfWork uow = entityManager.unwrap(UnitOfWork.class); 081 uow.getLogin().setTransactionIsolation(definition.getIsolationLevel()); 082 } 083 084 entityManager.getTransaction().begin(); 085 086 if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) { 087 // Begin an early transaction to force EclipseLink to get a JDBC Connection 088 // so that Spring can manage transactions with JDBC as well as EclipseLink. 089 entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction(); 090 } 091 092 return null; 093 } 094 095 @Override 096 public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) 097 throws PersistenceException, SQLException { 098 099 // As of Spring 4.1.2, we're using a custom ConnectionHandle for lazy retrieval 100 // of the underlying Connection (allowing for deferred internal transaction begin 101 // within the EclipseLink EntityManager) 102 return new EclipseLinkConnectionHandle(entityManager); 103 } 104 105 106 /** 107 * {@link ConnectionHandle} implementation that lazily fetches an 108 * EclipseLink-provided Connection on the first {@code getConnection} call - 109 * which may never come if no application code requests a JDBC Connection. 110 * This is useful to defer the early transaction begin that obtaining a 111 * JDBC Connection implies within an EclipseLink EntityManager. 112 */ 113 private static class EclipseLinkConnectionHandle implements ConnectionHandle { 114 115 private final EntityManager entityManager; 116 117 private Connection connection; 118 119 public EclipseLinkConnectionHandle(EntityManager entityManager) { 120 this.entityManager = entityManager; 121 } 122 123 @Override 124 public Connection getConnection() { 125 if (this.connection == null) { 126 this.connection = this.entityManager.unwrap(Connection.class); 127 } 128 return this.connection; 129 } 130 131 @Override 132 public void releaseConnection(Connection con) { 133 } 134 } 135 136}