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}