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.apache.commons.logging.LogFactory;
025import org.apache.openjpa.persistence.FetchPlan;
026import org.apache.openjpa.persistence.OpenJPAEntityManager;
027import org.apache.openjpa.persistence.OpenJPAPersistence;
028import org.apache.openjpa.persistence.jdbc.IsolationLevel;
029import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan;
030
031import org.springframework.jdbc.datasource.ConnectionHandle;
032import org.springframework.jdbc.datasource.ConnectionHolder;
033import org.springframework.jdbc.support.JdbcUtils;
034import org.springframework.orm.jpa.DefaultJpaDialect;
035import org.springframework.transaction.SavepointManager;
036import org.springframework.transaction.TransactionDefinition;
037import org.springframework.transaction.TransactionException;
038
039/**
040 * {@link org.springframework.orm.jpa.JpaDialect} implementation for Apache OpenJPA.
041 * Developed and tested against OpenJPA 2.2.
042 *
043 * @author Juergen Hoeller
044 * @author Costin Leau
045 * @since 2.0
046 */
047@SuppressWarnings("serial")
048public class OpenJpaDialect extends DefaultJpaDialect {
049
050        @Override
051        public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)
052                        throws PersistenceException, SQLException, TransactionException {
053
054                OpenJPAEntityManager openJpaEntityManager = getOpenJPAEntityManager(entityManager);
055
056                if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
057                        // Pass custom isolation level on to OpenJPA's JDBCFetchPlan configuration
058                        FetchPlan fetchPlan = openJpaEntityManager.getFetchPlan();
059                        if (fetchPlan instanceof JDBCFetchPlan) {
060                                IsolationLevel isolation = IsolationLevel.fromConnectionConstant(definition.getIsolationLevel());
061                                ((JDBCFetchPlan) fetchPlan).setIsolation(isolation);
062                        }
063                }
064
065                entityManager.getTransaction().begin();
066
067                if (!definition.isReadOnly()) {
068                        // Like with EclipseLink, make sure to start the logic transaction early so that other
069                        // participants using the connection (such as JdbcTemplate) run in a transaction.
070                        openJpaEntityManager.beginStore();
071                }
072
073                // Custom implementation for OpenJPA savepoint handling
074                return new OpenJpaTransactionData(openJpaEntityManager);
075        }
076
077        @Override
078        public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly)
079                        throws PersistenceException, SQLException {
080
081                return new OpenJpaConnectionHandle(getOpenJPAEntityManager(entityManager));
082        }
083
084        /**
085         * Return the OpenJPA-specific variant of {@code EntityManager}.
086         * @param em the generic {@code EntityManager} instance
087         * @return the OpenJPA-specific variant of {@code EntityManager}
088         */
089        protected OpenJPAEntityManager getOpenJPAEntityManager(EntityManager em) {
090                return OpenJPAPersistence.cast(em);
091        }
092
093
094        /**
095         * Transaction data Object exposed from {@code beginTransaction},
096         * implementing the {@link SavepointManager} interface.
097         */
098        private static class OpenJpaTransactionData implements SavepointManager {
099
100                private final OpenJPAEntityManager entityManager;
101
102                private int savepointCounter = 0;
103
104                public OpenJpaTransactionData(OpenJPAEntityManager entityManager) {
105                        this.entityManager = entityManager;
106                }
107
108                @Override
109                public Object createSavepoint() throws TransactionException {
110                        this.savepointCounter++;
111                        String savepointName = ConnectionHolder.SAVEPOINT_NAME_PREFIX + this.savepointCounter;
112                        this.entityManager.setSavepoint(savepointName);
113                        return savepointName;
114                }
115
116                @Override
117                public void rollbackToSavepoint(Object savepoint) throws TransactionException {
118                        this.entityManager.rollbackToSavepoint((String) savepoint);
119                }
120
121                @Override
122                public void releaseSavepoint(Object savepoint) throws TransactionException {
123                        try {
124                                this.entityManager.releaseSavepoint((String) savepoint);
125                        }
126                        catch (Throwable ex) {
127                                LogFactory.getLog(OpenJpaTransactionData.class).debug(
128                                                "Could not explicitly release OpenJPA savepoint", ex);
129                        }
130                }
131        }
132
133
134        /**
135         * {@link ConnectionHandle} implementation that fetches a new OpenJPA-provided
136         * Connection for every {@code getConnection} call and closes the Connection on
137         * {@code releaseConnection}. This is necessary because OpenJPA requires the
138         * fetched Connection to be closed before continuing EntityManager work.
139         * @see org.apache.openjpa.persistence.OpenJPAEntityManager#getConnection()
140         */
141        private static class OpenJpaConnectionHandle implements ConnectionHandle {
142
143                private final OpenJPAEntityManager entityManager;
144
145                public OpenJpaConnectionHandle(OpenJPAEntityManager entityManager) {
146                        this.entityManager = entityManager;
147                }
148
149                @Override
150                public Connection getConnection() {
151                        return (Connection) this.entityManager.getConnection();
152                }
153
154                @Override
155                public void releaseConnection(Connection con) {
156                        JdbcUtils.closeConnection(con);
157                }
158        }
159
160}