001/* 002 * Copyright 2002-2018 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; 021 022import javax.persistence.EntityManager; 023import javax.persistence.PersistenceException; 024 025import org.eclipse.persistence.sessions.UnitOfWork; 026 027import org.springframework.jdbc.datasource.ConnectionHandle; 028import org.springframework.lang.Nullable; 029import org.springframework.orm.jpa.DefaultJpaDialect; 030import org.springframework.transaction.TransactionDefinition; 031import org.springframework.transaction.TransactionException; 032 033/** 034 * {@link org.springframework.orm.jpa.JpaDialect} implementation for Eclipse 035 * Persistence Services (EclipseLink). Developed and tested against EclipseLink 2.7; 036 * backwards-compatible with EclipseLink 2.5 and 2.6 at runtime. 037 * 038 * <p>By default, this class acquires an early EclipseLink transaction with an early 039 * JDBC Connection for non-read-only transactions. This allows for mixing JDBC and 040 * JPA/EclipseLink operations in the same transaction, with cross visibility of 041 * their impact. If this is not needed, set the "lazyDatabaseTransaction" flag to 042 * {@code true} or consistently declare all affected transactions as read-only. 043 * As of Spring 4.1.2, this will reliably avoid early JDBC Connection retrieval 044 * and therefore keep EclipseLink in shared cache mode. 045 * 046 * @author Juergen Hoeller 047 * @since 2.5.2 048 * @see #setLazyDatabaseTransaction 049 * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy 050 */ 051@SuppressWarnings("serial") 052public class EclipseLinkJpaDialect extends DefaultJpaDialect { 053 054 private boolean lazyDatabaseTransaction = false; 055 056 057 /** 058 * Set whether to lazily start a database resource transaction within a 059 * Spring-managed EclipseLink transaction. 060 * <p>By default, read-only transactions are started lazily but regular 061 * non-read-only transactions are started early. This allows for reusing the 062 * same JDBC Connection throughout an entire EclipseLink transaction, for 063 * enforced isolation and consistent visibility with JDBC access code working 064 * on the same DataSource. 065 * <p>Switch this flag to "true" to enforce a lazy database transaction begin 066 * even for non-read-only transactions, allowing access to EclipseLink's 067 * shared cache and following EclipseLink's connection mode configuration, 068 * assuming that isolation and visibility at the JDBC level are less important. 069 * @see org.eclipse.persistence.sessions.UnitOfWork#beginEarlyTransaction() 070 */ 071 public void setLazyDatabaseTransaction(boolean lazyDatabaseTransaction) { 072 this.lazyDatabaseTransaction = lazyDatabaseTransaction; 073 } 074 075 076 @Override 077 @Nullable 078 public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition) 079 throws PersistenceException, SQLException, TransactionException { 080 081 if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { 082 // Pass custom isolation level on to EclipseLink's DatabaseLogin configuration 083 // (since Spring 4.1.2) 084 UnitOfWork uow = entityManager.unwrap(UnitOfWork.class); 085 uow.getLogin().setTransactionIsolation(definition.getIsolationLevel()); 086 } 087 088 entityManager.getTransaction().begin(); 089 090 if (!definition.isReadOnly() && !this.lazyDatabaseTransaction) { 091 // Begin an early transaction to force EclipseLink to get a JDBC Connection 092 // so that Spring can manage transactions with JDBC as well as EclipseLink. 093 entityManager.unwrap(UnitOfWork.class).beginEarlyTransaction(); 094 } 095 096 return null; 097 } 098 099 @Override 100 public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) 101 throws PersistenceException, SQLException { 102 103 // As of Spring 4.1.2, we're using a custom ConnectionHandle for lazy retrieval 104 // of the underlying Connection (allowing for deferred internal transaction begin 105 // within the EclipseLink EntityManager) 106 return new EclipseLinkConnectionHandle(entityManager); 107 } 108 109 110 /** 111 * {@link ConnectionHandle} implementation that lazily fetches an 112 * EclipseLink-provided Connection on the first {@code getConnection} call - 113 * which may never come if no application code requests a JDBC Connection. 114 * This is useful to defer the early transaction begin that obtaining a 115 * JDBC Connection implies within an EclipseLink EntityManager. 116 */ 117 private static class EclipseLinkConnectionHandle implements ConnectionHandle { 118 119 private final EntityManager entityManager; 120 121 @Nullable 122 private Connection connection; 123 124 public EclipseLinkConnectionHandle(EntityManager entityManager) { 125 this.entityManager = entityManager; 126 } 127 128 @Override 129 public Connection getConnection() { 130 if (this.connection == null) { 131 this.connection = this.entityManager.unwrap(Connection.class); 132 } 133 return this.connection; 134 } 135 } 136 137}