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 javax.persistence.PersistenceException;
020
021import org.hibernate.HibernateException;
022import org.hibernate.JDBCException;
023
024import org.springframework.dao.DataAccessException;
025import org.springframework.dao.support.PersistenceExceptionTranslator;
026import org.springframework.jdbc.support.SQLExceptionTranslator;
027import org.springframework.lang.Nullable;
028import org.springframework.orm.jpa.EntityManagerFactoryUtils;
029
030/**
031 * {@link PersistenceExceptionTranslator} capable of translating {@link HibernateException}
032 * instances to Spring's {@link DataAccessException} hierarchy. As of Spring 4.3.2 and
033 * Hibernate 5.2, it also converts standard JPA {@link PersistenceException} instances.
034 *
035 * <p>Extended by {@link LocalSessionFactoryBean}, so there is no need to declare this
036 * translator in addition to a {@code LocalSessionFactoryBean}.
037 *
038 * <p>When configuring the container with {@code @Configuration} classes, a {@code @Bean}
039 * of this type must be registered manually.
040 *
041 * @author Juergen Hoeller
042 * @since 4.2
043 * @see org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
044 * @see SessionFactoryUtils#convertHibernateAccessException(HibernateException)
045 * @see EntityManagerFactoryUtils#convertJpaAccessExceptionIfPossible(RuntimeException)
046 */
047public class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
048
049        @Nullable
050        private SQLExceptionTranslator jdbcExceptionTranslator;
051
052
053        /**
054         * Set the JDBC exception translator for Hibernate exception translation purposes.
055         * <p>Applied to any detected {@link java.sql.SQLException} root cause of a Hibernate
056         * {@link JDBCException}, overriding Hibernate's own {@code SQLException} translation
057         * (which is based on a Hibernate Dialect for a specific target database).
058         * @since 5.1
059         * @see java.sql.SQLException
060         * @see org.hibernate.JDBCException
061         * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
062         * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
063         */
064        public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
065                this.jdbcExceptionTranslator = jdbcExceptionTranslator;
066        }
067
068
069        @Override
070        @Nullable
071        public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
072                if (ex instanceof HibernateException) {
073                        return convertHibernateAccessException((HibernateException) ex);
074                }
075                if (ex instanceof PersistenceException) {
076                        if (ex.getCause() instanceof HibernateException) {
077                                return convertHibernateAccessException((HibernateException) ex.getCause());
078                        }
079                        return EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex);
080                }
081                return null;
082        }
083
084        /**
085         * Convert the given HibernateException to an appropriate exception from the
086         * {@code org.springframework.dao} hierarchy.
087         * <p>Will automatically apply a specified SQLExceptionTranslator to a
088         * Hibernate JDBCException, otherwise rely on Hibernate's default translation.
089         * @param ex the HibernateException that occurred
090         * @return a corresponding DataAccessException
091         * @see SessionFactoryUtils#convertHibernateAccessException
092         */
093        protected DataAccessException convertHibernateAccessException(HibernateException ex) {
094                if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) {
095                        JDBCException jdbcEx = (JDBCException) ex;
096                        DataAccessException dae = this.jdbcExceptionTranslator.translate(
097                                        "Hibernate operation: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
098                        if (dae != null) {
099                                throw dae;
100                        }
101                }
102                return SessionFactoryUtils.convertHibernateAccessException(ex);
103        }
104
105}