001/* 002 * Copyright 2002-2013 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.jdo; 018 019import java.sql.Connection; 020import java.sql.SQLException; 021import javax.jdo.Constants; 022import javax.jdo.JDOException; 023import javax.jdo.PersistenceManager; 024import javax.jdo.Transaction; 025 026import org.springframework.dao.DataAccessException; 027import org.springframework.dao.support.PersistenceExceptionTranslator; 028import org.springframework.jdbc.datasource.ConnectionHandle; 029import org.springframework.jdbc.support.JdbcUtils; 030import org.springframework.jdbc.support.SQLExceptionTranslator; 031import org.springframework.transaction.TransactionDefinition; 032import org.springframework.transaction.TransactionException; 033 034/** 035 * Default implementation of the {@link JdoDialect} interface. 036 * As of Spring 4.0, designed for JDO 3.0 (or rather, semantics beyond JDO 3.0). 037 * Used as default dialect by {@link JdoTransactionManager}. 038 * 039 * <p>Simply begins a standard JDO transaction in {@code beginTransaction}. 040 * Returns a handle for a JDO DataStoreConnection on {@code getJdbcConnection}. 041 * Calls the corresponding JDO PersistenceManager operation on {@code flush} 042 * Uses a Spring SQLExceptionTranslator for exception translation, if applicable. 043 * 044 * <p>Note that, even with JDO 3.0, vendor-specific subclasses are still necessary 045 * for special transaction semantics and more sophisticated exception translation. 046 * Furthermore, vendor-specific subclasses are encouraged to expose the native JDBC 047 * Connection on {@code getJdbcConnection}, rather than JDO 3.0's wrapper handle. 048 * 049 * <p>This class also implements the PersistenceExceptionTranslator interface, 050 * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor, 051 * for AOP-based translation of native exceptions to Spring DataAccessExceptions. 052 * Hence, the presence of a standard DefaultJdoDialect bean automatically enables 053 * a PersistenceExceptionTranslationPostProcessor to translate JDO exceptions. 054 * 055 * @author Juergen Hoeller 056 * @since 1.1 057 * @see #setJdbcExceptionTranslator 058 * @see JdoTransactionManager#setJdoDialect 059 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor 060 */ 061public class DefaultJdoDialect implements JdoDialect, PersistenceExceptionTranslator { 062 063 private SQLExceptionTranslator jdbcExceptionTranslator; 064 065 066 /** 067 * Create a new DefaultJdoDialect. 068 */ 069 public DefaultJdoDialect() { 070 } 071 072 /** 073 * Create a new DefaultJdoDialect. 074 * @param connectionFactory the connection factory of the JDO PersistenceManagerFactory, 075 * which is used to initialize the default JDBC exception translator 076 * @see javax.jdo.PersistenceManagerFactory#getConnectionFactory() 077 * @see PersistenceManagerFactoryUtils#newJdbcExceptionTranslator(Object) 078 */ 079 public DefaultJdoDialect(Object connectionFactory) { 080 this.jdbcExceptionTranslator = PersistenceManagerFactoryUtils.newJdbcExceptionTranslator(connectionFactory); 081 } 082 083 /** 084 * Set the JDBC exception translator for this dialect. 085 * <p>Applied to any SQLException root cause of a JDOException, if specified. 086 * The default is to rely on the JDO provider's native exception translation. 087 * @param jdbcExceptionTranslator exception translator 088 * @see java.sql.SQLException 089 * @see javax.jdo.JDOException#getCause() 090 * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator 091 * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator 092 */ 093 public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) { 094 this.jdbcExceptionTranslator = jdbcExceptionTranslator; 095 } 096 097 /** 098 * Return the JDBC exception translator for this dialect, if any. 099 */ 100 public SQLExceptionTranslator getJdbcExceptionTranslator() { 101 return this.jdbcExceptionTranslator; 102 } 103 104 105 //------------------------------------------------------------------------- 106 // Hooks for transaction management (used by JdoTransactionManager) 107 //------------------------------------------------------------------------- 108 109 /** 110 * This implementation invokes the standard JDO {@link Transaction#begin()} 111 * method and also {@link Transaction#setIsolationLevel(String)} if necessary. 112 * @see javax.jdo.Transaction#begin 113 * @see org.springframework.transaction.InvalidIsolationLevelException 114 */ 115 @Override 116 public Object beginTransaction(Transaction transaction, TransactionDefinition definition) 117 throws JDOException, SQLException, TransactionException { 118 119 String jdoIsolationLevel = getJdoIsolationLevel(definition); 120 if (jdoIsolationLevel != null) { 121 transaction.setIsolationLevel(jdoIsolationLevel); 122 } 123 transaction.begin(); 124 return null; 125 } 126 127 /** 128 * Determine the JDO isolation level String to use for the given 129 * Spring transaction definition. 130 * @param definition the Spring transaction definition 131 * @return the corresponding JDO isolation level String, or {@code null} 132 * to indicate that no isolation level should be set explicitly 133 * @see Transaction#setIsolationLevel(String) 134 * @see Constants#TX_SERIALIZABLE 135 * @see Constants#TX_REPEATABLE_READ 136 * @see Constants#TX_READ_COMMITTED 137 * @see Constants#TX_READ_UNCOMMITTED 138 */ 139 protected String getJdoIsolationLevel(TransactionDefinition definition) { 140 switch (definition.getIsolationLevel()) { 141 case TransactionDefinition.ISOLATION_SERIALIZABLE: 142 return Constants.TX_SERIALIZABLE; 143 case TransactionDefinition.ISOLATION_REPEATABLE_READ: 144 return Constants.TX_REPEATABLE_READ; 145 case TransactionDefinition.ISOLATION_READ_COMMITTED: 146 return Constants.TX_READ_COMMITTED; 147 case TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 148 return Constants.TX_READ_UNCOMMITTED; 149 default: 150 return null; 151 } 152 } 153 154 /** 155 * This implementation does nothing, as the default beginTransaction implementation 156 * does not require any cleanup. 157 * @see #beginTransaction 158 */ 159 @Override 160 public void cleanupTransaction(Object transactionData) { 161 } 162 163 /** 164 * This implementation returns a DataStoreConnectionHandle for JDO. 165 * <p><b>NOTE:</b> A JDO DataStoreConnection is always a wrapper, 166 * never the native JDBC Connection. If you need access to the native JDBC 167 * Connection (or the connection pool handle, to be unwrapped via a Spring 168 * NativeJdbcExtractor), override this method to return the native 169 * Connection through the corresponding vendor-specific mechanism. 170 * <p>A JDO DataStoreConnection is only "borrowed" from the PersistenceManager: 171 * it needs to be returned as early as possible. Effectively, JDO requires the 172 * fetched Connection to be closed before continuing PersistenceManager work. 173 * For this reason, the exposed ConnectionHandle eagerly releases its JDBC 174 * Connection at the end of each JDBC data access operation (that is, on 175 * {@code DataSourceUtils.releaseConnection}). 176 * @see javax.jdo.PersistenceManager#getDataStoreConnection() 177 * @see org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor 178 * @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection 179 */ 180 @Override 181 public ConnectionHandle getJdbcConnection(PersistenceManager pm, boolean readOnly) 182 throws JDOException, SQLException { 183 184 return new DataStoreConnectionHandle(pm); 185 } 186 187 /** 188 * This implementation does nothing, assuming that the Connection 189 * will implicitly be closed with the PersistenceManager. 190 * <p>If the JDO provider returns a Connection handle that it 191 * expects the application to close, the dialect needs to invoke 192 * {@code Connection.close} here. 193 * @see java.sql.Connection#close() 194 */ 195 @Override 196 public void releaseJdbcConnection(ConnectionHandle conHandle, PersistenceManager pm) 197 throws JDOException, SQLException { 198 } 199 200 201 //----------------------------------------------------------------------------------- 202 // Hook for exception translation (used by JdoTransactionManager) 203 //----------------------------------------------------------------------------------- 204 205 /** 206 * Implementation of the PersistenceExceptionTranslator interface, 207 * as autodetected by Spring's PersistenceExceptionTranslationPostProcessor. 208 * <p>Converts the exception if it is a JDOException, using this JdoDialect. 209 * Else returns {@code null} to indicate an unknown exception. 210 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor 211 * @see #translateException 212 */ 213 @Override 214 public DataAccessException translateExceptionIfPossible(RuntimeException ex) { 215 if (ex instanceof JDOException) { 216 return translateException((JDOException) ex); 217 } 218 return null; 219 } 220 221 /** 222 * This implementation delegates to PersistenceManagerFactoryUtils. 223 * @see PersistenceManagerFactoryUtils#convertJdoAccessException 224 */ 225 @Override 226 public DataAccessException translateException(JDOException ex) { 227 if (getJdbcExceptionTranslator() != null && ex.getCause() instanceof SQLException) { 228 return getJdbcExceptionTranslator().translate("JDO operation: " + ex.getMessage(), 229 extractSqlStringFromException(ex), (SQLException) ex.getCause()); 230 } 231 return PersistenceManagerFactoryUtils.convertJdoAccessException(ex); 232 } 233 234 /** 235 * Template method for extracting a SQL String from the given exception. 236 * <p>Default implementation always returns {@code null}. Can be overridden in 237 * subclasses to extract SQL Strings for vendor-specific exception classes. 238 * @param ex the JDOException, containing a SQLException 239 * @return the SQL String, or {@code null} if none found 240 */ 241 protected String extractSqlStringFromException(JDOException ex) { 242 return null; 243 } 244 245 246 /** 247 * ConnectionHandle implementation that fetches a new JDO DataStoreConnection 248 * for every {@code getConnection} call and closes the Connection on 249 * {@code releaseConnection}. This is necessary because JDO requires the 250 * fetched Connection to be closed before continuing PersistenceManager work. 251 * @see javax.jdo.PersistenceManager#getDataStoreConnection() 252 */ 253 private static class DataStoreConnectionHandle implements ConnectionHandle { 254 255 private final PersistenceManager persistenceManager; 256 257 public DataStoreConnectionHandle(PersistenceManager persistenceManager) { 258 this.persistenceManager = persistenceManager; 259 } 260 261 @Override 262 public Connection getConnection() { 263 return (Connection) this.persistenceManager.getDataStoreConnection(); 264 } 265 266 @Override 267 public void releaseConnection(Connection con) { 268 JdbcUtils.closeConnection(con); 269 } 270 } 271 272}