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.hibernate5; 018 019import java.lang.reflect.Method; 020import java.util.Map; 021import javax.persistence.PersistenceException; 022import javax.sql.DataSource; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.hibernate.FlushMode; 027import org.hibernate.HibernateException; 028import org.hibernate.JDBCException; 029import org.hibernate.NonUniqueObjectException; 030import org.hibernate.NonUniqueResultException; 031import org.hibernate.ObjectDeletedException; 032import org.hibernate.PersistentObjectException; 033import org.hibernate.PessimisticLockException; 034import org.hibernate.PropertyValueException; 035import org.hibernate.QueryException; 036import org.hibernate.QueryTimeoutException; 037import org.hibernate.Session; 038import org.hibernate.SessionFactory; 039import org.hibernate.StaleObjectStateException; 040import org.hibernate.StaleStateException; 041import org.hibernate.TransientObjectException; 042import org.hibernate.UnresolvableObjectException; 043import org.hibernate.WrongClassException; 044import org.hibernate.cfg.Environment; 045import org.hibernate.dialect.lock.OptimisticEntityLockException; 046import org.hibernate.dialect.lock.PessimisticEntityLockException; 047import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; 048import org.hibernate.engine.spi.SessionFactoryImplementor; 049import org.hibernate.exception.ConstraintViolationException; 050import org.hibernate.exception.DataException; 051import org.hibernate.exception.JDBCConnectionException; 052import org.hibernate.exception.LockAcquisitionException; 053import org.hibernate.exception.SQLGrammarException; 054import org.hibernate.service.UnknownServiceException; 055 056import org.springframework.dao.CannotAcquireLockException; 057import org.springframework.dao.DataAccessException; 058import org.springframework.dao.DataAccessResourceFailureException; 059import org.springframework.dao.DataIntegrityViolationException; 060import org.springframework.dao.DuplicateKeyException; 061import org.springframework.dao.IncorrectResultSizeDataAccessException; 062import org.springframework.dao.InvalidDataAccessApiUsageException; 063import org.springframework.dao.InvalidDataAccessResourceUsageException; 064import org.springframework.dao.PessimisticLockingFailureException; 065import org.springframework.jdbc.datasource.DataSourceUtils; 066import org.springframework.util.Assert; 067import org.springframework.util.ClassUtils; 068import org.springframework.util.ReflectionUtils; 069 070/** 071 * Helper class featuring methods for Hibernate Session handling. 072 * Also provides support for exception translation. 073 * 074 * <p>Used internally by {@link HibernateTransactionManager}. 075 * Can also be used directly in application code. 076 * 077 * @author Juergen Hoeller 078 * @since 4.2 079 * @see HibernateExceptionTranslator 080 * @see HibernateTransactionManager 081 */ 082public abstract class SessionFactoryUtils { 083 084 /** 085 * Order value for TransactionSynchronization objects that clean up Hibernate Sessions. 086 * Returns {@code DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100} 087 * to execute Session cleanup before JDBC Connection cleanup, if any. 088 * @see DataSourceUtils#CONNECTION_SYNCHRONIZATION_ORDER 089 */ 090 public static final int SESSION_SYNCHRONIZATION_ORDER = 091 DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 100; 092 093 static final Log logger = LogFactory.getLog(SessionFactoryUtils.class); 094 095 096 private static Method getFlushMode; 097 098 static { 099 try { 100 // Hibernate 5.2+ getHibernateFlushMode() 101 getFlushMode = Session.class.getMethod("getHibernateFlushMode"); 102 } 103 catch (NoSuchMethodException ex) { 104 try { 105 // Hibernate 5.0/5.1 getFlushMode() with FlushMode return type 106 getFlushMode = Session.class.getMethod("getFlushMode"); 107 } 108 catch (NoSuchMethodException ex2) { 109 throw new IllegalStateException("No compatible Hibernate getFlushMode signature found", ex2); 110 } 111 } 112 // Check that it is the Hibernate FlushMode type, not JPA's... 113 Assert.state(FlushMode.class == getFlushMode.getReturnType(), "Could not find Hibernate getFlushMode method"); 114 } 115 116 117 /** 118 * Get the native Hibernate FlushMode, adapting between Hibernate 5.0/5.1 and 5.2+. 119 * @param session the Hibernate Session to get the flush mode from 120 * @return the FlushMode (never {@code null}) 121 * @since 4.3 122 */ 123 static FlushMode getFlushMode(Session session) { 124 return (FlushMode) ReflectionUtils.invokeMethod(getFlushMode, session); 125 } 126 127 /** 128 * Trigger a flush on the given Hibernate Session, converting regular 129 * {@link HibernateException} instances as well as Hibernate 5.2's 130 * {@link PersistenceException} wrappers accordingly. 131 * @param session the Hibernate Session to flush 132 * @param synch whether this flush is triggered by transaction synchronization 133 * @throws DataAccessException in case of flush failures 134 * @since 4.3.2 135 */ 136 static void flush(Session session, boolean synch) throws DataAccessException { 137 if (synch) { 138 logger.debug("Flushing Hibernate Session on transaction synchronization"); 139 } 140 else { 141 logger.debug("Flushing Hibernate Session on explicit request"); 142 } 143 try { 144 session.flush(); 145 } 146 catch (HibernateException ex) { 147 throw convertHibernateAccessException(ex); 148 } 149 catch (PersistenceException ex) { 150 if (ex.getCause() instanceof HibernateException) { 151 throw convertHibernateAccessException((HibernateException) ex.getCause()); 152 } 153 throw ex; 154 } 155 156 } 157 158 /** 159 * Perform actual closing of the Hibernate Session, 160 * catching and logging any cleanup exceptions thrown. 161 * @param session the Hibernate Session to close (may be {@code null}) 162 * @see Session#close() 163 */ 164 public static void closeSession(Session session) { 165 if (session != null) { 166 try { 167 session.close(); 168 } 169 catch (HibernateException ex) { 170 logger.debug("Could not close Hibernate Session", ex); 171 } 172 catch (Throwable ex) { 173 logger.debug("Unexpected exception on closing Hibernate Session", ex); 174 } 175 } 176 } 177 178 /** 179 * Determine the DataSource of the given SessionFactory. 180 * @param sessionFactory the SessionFactory to check 181 * @return the DataSource, or {@code null} if none found 182 * @see ConnectionProvider 183 */ 184 public static DataSource getDataSource(SessionFactory sessionFactory) { 185 Method getProperties = ClassUtils.getMethodIfAvailable(sessionFactory.getClass(), "getProperties"); 186 if (getProperties != null) { 187 Map<?, ?> props = (Map<?, ?>) ReflectionUtils.invokeMethod(getProperties, sessionFactory); 188 Object dataSourceValue = props.get(Environment.DATASOURCE); 189 if (dataSourceValue instanceof DataSource) { 190 return (DataSource) dataSourceValue; 191 } 192 } 193 if (sessionFactory instanceof SessionFactoryImplementor) { 194 SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory; 195 try { 196 ConnectionProvider cp = sfi.getServiceRegistry().getService(ConnectionProvider.class); 197 if (cp != null) { 198 return cp.unwrap(DataSource.class); 199 } 200 } 201 catch (UnknownServiceException ex) { 202 if (logger.isDebugEnabled()) { 203 logger.debug("No ConnectionProvider found - cannot determine DataSource for SessionFactory: " + ex); 204 } 205 } 206 } 207 return null; 208 } 209 210 /** 211 * Convert the given HibernateException to an appropriate exception 212 * from the {@code org.springframework.dao} hierarchy. 213 * @param ex HibernateException that occurred 214 * @return the corresponding DataAccessException instance 215 * @see HibernateExceptionTranslator#convertHibernateAccessException 216 * @see HibernateTransactionManager#convertHibernateAccessException 217 */ 218 public static DataAccessException convertHibernateAccessException(HibernateException ex) { 219 if (ex instanceof JDBCConnectionException) { 220 return new DataAccessResourceFailureException(ex.getMessage(), ex); 221 } 222 if (ex instanceof SQLGrammarException) { 223 SQLGrammarException jdbcEx = (SQLGrammarException) ex; 224 return new InvalidDataAccessResourceUsageException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 225 } 226 if (ex instanceof QueryTimeoutException) { 227 QueryTimeoutException jdbcEx = (QueryTimeoutException) ex; 228 return new org.springframework.dao.QueryTimeoutException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 229 } 230 if (ex instanceof LockAcquisitionException) { 231 LockAcquisitionException jdbcEx = (LockAcquisitionException) ex; 232 return new CannotAcquireLockException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 233 } 234 if (ex instanceof PessimisticLockException) { 235 PessimisticLockException jdbcEx = (PessimisticLockException) ex; 236 return new PessimisticLockingFailureException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 237 } 238 if (ex instanceof ConstraintViolationException) { 239 ConstraintViolationException jdbcEx = (ConstraintViolationException) ex; 240 return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + 241 "]; constraint [" + jdbcEx.getConstraintName() + "]", ex); 242 } 243 if (ex instanceof DataException) { 244 DataException jdbcEx = (DataException) ex; 245 return new DataIntegrityViolationException(ex.getMessage() + "; SQL [" + jdbcEx.getSQL() + "]", ex); 246 } 247 if (ex instanceof JDBCException) { 248 return new HibernateJdbcException((JDBCException) ex); 249 } 250 // end of JDBCException (subclass) handling 251 252 if (ex instanceof QueryException) { 253 return new HibernateQueryException((QueryException) ex); 254 } 255 if (ex instanceof NonUniqueResultException) { 256 return new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex); 257 } 258 if (ex instanceof NonUniqueObjectException) { 259 return new DuplicateKeyException(ex.getMessage(), ex); 260 } 261 if (ex instanceof PropertyValueException) { 262 return new DataIntegrityViolationException(ex.getMessage(), ex); 263 } 264 if (ex instanceof PersistentObjectException) { 265 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 266 } 267 if (ex instanceof TransientObjectException) { 268 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 269 } 270 if (ex instanceof ObjectDeletedException) { 271 return new InvalidDataAccessApiUsageException(ex.getMessage(), ex); 272 } 273 if (ex instanceof UnresolvableObjectException) { 274 return new HibernateObjectRetrievalFailureException((UnresolvableObjectException) ex); 275 } 276 if (ex instanceof WrongClassException) { 277 return new HibernateObjectRetrievalFailureException((WrongClassException) ex); 278 } 279 if (ex instanceof StaleObjectStateException) { 280 return new HibernateOptimisticLockingFailureException((StaleObjectStateException) ex); 281 } 282 if (ex instanceof StaleStateException) { 283 return new HibernateOptimisticLockingFailureException((StaleStateException) ex); 284 } 285 if (ex instanceof OptimisticEntityLockException) { 286 return new HibernateOptimisticLockingFailureException((OptimisticEntityLockException) ex); 287 } 288 if (ex instanceof PessimisticEntityLockException) { 289 if (ex.getCause() instanceof LockAcquisitionException) { 290 return new CannotAcquireLockException(ex.getMessage(), ex.getCause()); 291 } 292 return new PessimisticLockingFailureException(ex.getMessage(), ex); 293 } 294 295 // fallback 296 return new HibernateSystemException(ex); 297 } 298 299}