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