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);
293294
295                // fallback
296                return new HibernateSystemException(ex);
297        }
298
299}