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}