View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.springframework.orm.jpa.vendor;
17  
18  import java.sql.Connection;
19  import java.sql.SQLException;
20  import javax.persistence.EntityManager;
21  import javax.persistence.PersistenceException;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.openjpa.persistence.FetchPlan;
24  import org.apache.openjpa.persistence.OpenJPAEntityManager;
25  import org.apache.openjpa.persistence.OpenJPAPersistence;
26  import org.apache.openjpa.persistence.jdbc.IsolationLevel;
27  import org.apache.openjpa.persistence.jdbc.JDBCFetchPlan;
28  import org.springframework.jdbc.datasource.ConnectionHandle;
29  import org.springframework.jdbc.datasource.ConnectionHolder;
30  import org.springframework.jdbc.support.JdbcUtils;
31  import org.springframework.orm.jpa.DefaultJpaDialect;
32  import org.springframework.transaction.SavepointManager;
33  import org.springframework.transaction.TransactionDefinition;
34  import org.springframework.transaction.TransactionException;
35  
36  /**
37   * {@link org.springframework.orm.jpa.JpaDialect} implementation for Apache OpenJPA.
38   * Developed and tested against OpenJPA 2.2.
39   *
40   * @author Juergen Hoeller
41   * @author Costin Leau
42   * @since 2.0
43   */
44  public class OpenJpaDialect extends DefaultJpaDialect {
45  
46      private static final long serialVersionUID = 2099118508988476959L;
47  
48      @Override
49      public Object beginTransaction(final EntityManager entityManager, final TransactionDefinition definition)
50              throws PersistenceException, SQLException, TransactionException {
51  
52          OpenJPAEntityManager openJpaEntityManager = getOpenJPAEntityManager(entityManager);
53  
54          if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
55              // Pass custom isolation level on to OpenJPA's JDBCFetchPlan configuration
56              FetchPlan fetchPlan = openJpaEntityManager.getFetchPlan();
57              if (fetchPlan instanceof JDBCFetchPlan) {
58                  IsolationLevel isolation = IsolationLevel.fromConnectionConstant(definition.getIsolationLevel());
59                  ((JDBCFetchPlan) fetchPlan).setIsolation(isolation);
60              }
61          }
62  
63          entityManager.getTransaction().begin();
64  
65          if (!definition.isReadOnly()) {
66              // Like with EclipseLink, make sure to start the logic transaction early so that other
67              // participants using the connection (such as JdbcTemplate) run in a transaction.
68              openJpaEntityManager.beginStore();
69          }
70  
71          // Custom implementation for OpenJPA savepoint handling
72          return new OpenJpaTransactionData(openJpaEntityManager);
73      }
74  
75      @Override
76      public ConnectionHandle getJdbcConnection(final EntityManager entityManager, final boolean readOnly)
77              throws PersistenceException, SQLException {
78  
79          return new OpenJpaConnectionHandle(getOpenJPAEntityManager(entityManager));
80      }
81  
82      /**
83       * Return the OpenJPA-specific variant of {@code EntityManager}.
84       *
85       * @param em the generic {@code EntityManager} instance
86       * @return the OpenJPA-specific variant of {@code EntityManager}
87       */
88      protected static OpenJPAEntityManager getOpenJPAEntityManager(final EntityManager em) {
89          return OpenJPAPersistence.cast(em);
90      }
91  
92      /**
93       * Transaction data Object exposed from {@code beginTransaction},
94       * implementing the {@link SavepointManager} interface.
95       */
96      private static class OpenJpaTransactionData implements SavepointManager {
97  
98          private final OpenJPAEntityManager entityManager;
99  
100         private int savepointCounter = 0;
101 
102         OpenJpaTransactionData(final OpenJPAEntityManager entityManager) {
103             this.entityManager = entityManager;
104         }
105 
106         @Override
107         public Object createSavepoint() throws TransactionException {
108             this.savepointCounter++;
109             String savepointName = ConnectionHolder.SAVEPOINT_NAME_PREFIX + this.savepointCounter;
110             this.entityManager.setSavepoint(savepointName);
111             return savepointName;
112         }
113 
114         @Override
115         public void rollbackToSavepoint(final Object savepoint) throws TransactionException {
116             this.entityManager.rollbackToSavepoint((String) savepoint);
117         }
118 
119         @Override
120         public void releaseSavepoint(final Object savepoint) throws TransactionException {
121             try {
122                 this.entityManager.releaseSavepoint((String) savepoint);
123             } catch (Throwable ex) {
124                 LogFactory.getLog(OpenJpaTransactionData.class).debug(
125                         "Could not explicitly release OpenJPA savepoint", ex);
126             }
127         }
128     }
129 
130     /**
131      * {@link ConnectionHandle} implementation that fetches a new OpenJPA-provided
132      * Connection for every {@code getConnection} call and closes the Connection on
133      * {@code releaseConnection}. This is necessary because OpenJPA requires the
134      * fetched Connection to be closed before continuing EntityManager work.
135      *
136      * @see org.apache.openjpa.persistence.OpenJPAEntityManager#getConnection()
137      */
138     private static class OpenJpaConnectionHandle implements ConnectionHandle {
139 
140         private final OpenJPAEntityManager entityManager;
141 
142         OpenJpaConnectionHandle(final OpenJPAEntityManager entityManager) {
143             this.entityManager = entityManager;
144         }
145 
146         @Override
147         public Connection getConnection() {
148             return (Connection) this.entityManager.getConnection();
149         }
150 
151         @Override
152         public void releaseConnection(final Connection con) {
153             JdbcUtils.closeConnection(con);
154         }
155     }
156 }