Spring Boot中JPA-Hibernate无法将MySQL异常转为DataAccessException
我们的Spring Boot应用使用Hibernate配合MySQL数据库,当前遇到异常转换失效问题:当DAO层方法抛出java.sql.SQLException时,Spring并未将其转换为org.springframework.dao.DataAccessException体系异常,而是被Hibernate封装为javax.persistence.PersistenceException体系(例如查询无结果时抛出javax.persistence.NoResultException)。
相关配置
JPA-Hibernate配置类
package ....; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; @RequiredArgsConstructor @Configuration @EnableJpaRepositories @EnableTransactionManagement public class JpaConfig { @Value("${spring.profiles.active}") private String activeProfile; private final LocalContainerEntityManagerFactoryBean emf; @Bean public PlatformTransactionManager txManager() { JpaTransactionManager txManager = new JpaTransactionManager(); txManager.setEntityManagerFactory(emf.getObject()); txManager.setJpaDialect(emf.getJpaDialect()); txManager.setRollbackOnCommitFailure(true); txManager.setPersistenceUnitName(activeProfile); return txManager; } @Bean public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { return new PersistenceExceptionTranslationPostProcessor(); } }
应用属性配置
spring.datasource.url=jdbc:mysql://xxx spring.datasource.username=xxx spring.datasource.password=xxx spring.datasource.hikari.maximum-pool-size=20 spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.use_sql_comments=true spring.data.jpa.repositories.bootstrap_mode=lazy spring.datasource.name=${xxx} spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.hikari.pool-name=${xxx} spring.datasource.hikari.auto-commit=false spring.datasource.hikari.max-lifetime=600000 spring.datasource.hikari.connection-timeout=600000 spring.datasource.hikari.validation-timeout=600000 spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect spring.jpa.open-in-view=false spring.jpa.generate-ddl=false spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=false spring.jpa.properties.hibernate.connection.autocommit=false spring.jpa.properties.hibernate.ddl-auto=validate spring.jpa.properties.hibernate.format_sql=false spring.jpa.properties.hibernate.use_sql_comments=false
DAO层代码
@Repository public class XxxxxDaoImpl implements XxxxxDao { @Override public Entity getXXX(Long xxx) { try { return entityManager.createQuery("SELECT c FROM xxx c " + "INNER JOIN c.xx e ON e.id = :xxx " + "INNER JOIN FETCH xxx i", xxx.class) .setParameter("xxx", xxx).getSingleResult(); } catch(DataAccessException e) { // 无法进入此catch块,因为抛出的是javax.persistence.PersistenceException类型异常 log.error("DAO: exception in querying xxx for xxx: {} => {}", xxx, e.getMessage()); return null; } } }
日志提示
启动日志中出现如下提示,怀疑与异常转换失效相关:
INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'spring.sql.init-org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties' of type [org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'dataSourceScriptDatabaseInitializer' of type [org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'spring.jpa-org.springframework.boot.autoconfigure.orm.jpa.JpaProperties' of type [org.springframework.boot.autoconfigure.orm.jpa.JpaProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'spring.jpa.hibernate-org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties' of type [org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration' of type [org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'hikariPoolDataSourceMetadataProvider' of type [org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration$HikariPoolDataSourceMetadataProviderConfiguration$$Lambda$449/0x00000008403ab840] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration' of type [org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'jpaVendorAdapter' of type [org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying) INFO o.s.c.s.PostProcessorRegistrationDelegate$BeanPostProcessorChecker.postProcessAfterInitialization:376: Bean 'entityManagerFactoryBuilder' of type [org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
问题分析与解决方案建议
确认@Repository代理生效
Spring的PersistenceExceptionTranslationPostProcessor依赖AOP对标注@Repository的类生成代理实现异常转换。需确保XxxxxDaoImpl被Spring容器正确管理,可通过日志检查Bean初始化状态。确保EntityManager由Spring注入
若EntityManager是通过非Spring方式(如直接调用Persistence.createEntityManagerFactory)获取,Spring无法对其进行异常转换。必须使用@PersistenceContext注解注入EntityManager。调整异常转换处理器配置
显式开启CGLIB代理,避免JDK动态代理的局限性:@Bean public PersistenceExceptionTranslationPostProcessor exceptionTranslation() { PersistenceExceptionTranslationPostProcessor processor = new PersistenceExceptionTranslationPostProcessor(); processor.setProxyTargetClass(true); return processor; }修复Bean初始化顺序问题
日志中关于Bean不适合被后置处理器处理的提示,通常是因为Bean初始化早于后置处理器。可尝试移除自定义JpaConfig,改用Spring Boot自动配置;若必须保留自定义配置,确保LocalContainerEntityManagerFactoryBean由Spring Boot自动注入。手动转换异常(临时方案)
若上述方案无效,可在DAO层手动转换异常:@Autowired private PersistenceExceptionTranslator exceptionTranslator; @Override public Entity getXXX(Long xxx) { try { return entityManager.createQuery(...).getSingleResult(); } catch(PersistenceException e) { DataAccessException dae = exceptionTranslator.translateExceptionIfPossible(e); if (dae != null) { throw dae; } throw e; } }
内容的提问来源于stack exchange,提问作者Mohamad

