1. 简介
在使用 Hibernate 的过程中,我们可能会遇到各种各样的异常。这些异常可能源自对象关系映射(ORM)配置、数据库连接、SQL 执行、事务控制等多个方面。
本文将介绍一些常见的 Hibernate 异常,分析其产生的原因,并提供对应的解决方案。适合有一定 Hibernate 使用经验的 Java 开发者参考。
2. Hibernate 异常概览
Hibernate 中的异常大多继承自 HibernateException
,而当 Hibernate 作为 JPA 实现使用时,这些异常通常会被包装为 PersistenceException
。这两种异常都是运行时异常(RuntimeException
),因此不需要强制捕获。
大多数异常是不可恢复的,也就是说,重试操作并不会解决问题,遇到这类异常时通常需要放弃当前 Session。
接下来,我们将逐一介绍这些常见异常。
3. 映射错误
3.1. MappingException
当对象关系映射(ORM)配置出错时,Hibernate 会抛出 MappingException
:
public void whenQueryExecutedWithUnmappedEntity_thenMappingException() {
thrown.expect(isA(MappingException.class));
thrown.expectMessage("Unable to locate persister: com.baeldung.hibernate.exception.ProductNotMapped");
ProductNotMapped product = new ProductNotMapped();
product.setId(1);
product.setName("test");
Session session = sessionFactory.openSession();
session.save(product);
}
如果类没有被正确标记为 @Entity
,Hibernate 就无法识别该类的结构,从而抛出异常。
✅ 常见原因包括:
- 实体类未加
@Entity
- 字段和方法混合使用注解
@ManyToMany
缺少@JoinTable
- 映射类默认构造函数抛出异常
⚠️ MappingException
还有几个子类:
AnnotationException
:注解问题DuplicateMappingException
:重复映射InvalidMappingException
:无效映射MappingNotFoundException
:映射资源未找到PropertyNotFoundException
:getter/setter 未找到
3.2. AnnotationException
实体类缺少主键标识(@Id
)时会抛出此异常:
@Entity
public class EntityWithNoId {
private int id;
public int getId() {
return id;
}
}
public void givenEntityWithoutId_whenSessionFactoryCreated_thenAnnotationException() {
thrown.expect(isA(HibernateException.class));
thrown.expectMessage("Entity 'com.baeldung.hibernate.exception.EntityWithNoId' has no identifier...");
Configuration cfg = getConfiguration();
cfg.addAnnotatedClass(EntityWithNoId.class);
cfg.buildSessionFactory();
}
❌ 常见原因还包括:
@GeneratedValue
使用了未知的生成器- 在 Java 8 时间类上使用
@Temporal
@ManyToOne
缺少目标实体- 使用原始集合类如
ArrayList
而非接口
3.3. QuerySyntaxException
在 HQL 查询中使用表名而非实体名时会抛出此异常:
@Test
public void whenQueryExecutedWithInvalidClassName_thenQuerySyntaxException() {
thrown.expectCause(isA(UnknownEntityException.class));
thrown.expectMessage("Could not resolve root entity 'PRODUCT");
Session session = sessionFactory.openSession();
List<Product> results = session.createQuery("from PRODUCT", Product.class)
.getResultList();
}
✅ 解决方案:始终使用实体类名而非数据库表名。
4. 数据库结构管理错误
4.1. SchemaManagementException
当 Hibernate 校验数据库结构失败时会抛出此异常:
public void givenMissingTable_whenSchemaValidated_thenSchemaManagementException() {
thrown.expect(SchemaManagementException.class);
thrown.expectMessage("Schema-validation: missing table");
Configuration cfg = getConfiguration();
cfg.setProperty(AvailableSettings.HBM2DDL_AUTO, "validate");
cfg.addAnnotatedClass(Product.class);
cfg.buildSessionFactory();
}
4.2. CommandAcceptanceException
当执行 DDL 语句失败时会抛出此异常:
public void whenWrongDialectSpecified_thenCommandAcceptanceException() {
thrown.expect(SchemaManagementException.class);
thrown.expectCause(isA(CommandAcceptanceException.class));
thrown.expectMessage("Halting on error : Error executing DDL");
Configuration cfg = getConfiguration();
cfg.setProperty(AvailableSettings.DIALECT, "org.hibernate.dialect.MySQLDialect");
cfg.setProperty(AvailableSettings.HBM2DDL_AUTO, "update");
cfg.setProperty(AvailableSettings.HBM2DDL_HALT_ON_ERROR,"true");
cfg.addAnnotatedClass(Product.class);
cfg.buildSessionFactory();
}
✅ 解决方案:
- 确保数据库方言配置正确
- 验证表名、列名与数据库一致
- 用户权限足够
5. SQL 执行异常
5.1. JDBCException
所有由 JDBC 引发的异常都会被 Hibernate 包装为 JDBCException
或其子类。
5.2. SQLGrammarException
SQL 语法错误或表/列不存在时抛出:
@Test
public void givenMissingTable_whenQueryExecuted_thenSQLGrammarException() {
thrown.expectCause(isA(JdbcSqlSyntaxErrorException.class));
thrown.expectMessage("Table \"NON_EXISTING_TABLE\" not found");
Session session = sessionFactory.openSession();
NativeQuery<Product> query = session.createNativeQuery("select * from NON_EXISTING_TABLE", Product.class);
query.getResultList();
}
5.3. ConstraintViolationException
违反数据库约束时抛出,例如插入重复主键:
public void whenDuplicateIdSaved_thenConstraintViolationException() {
thrown.expectCause(isA(JdbcSQLIntegrityConstraintViolationException.class));
thrown.expectMessage("could not execute statement");
Session session = null;
Transaction transaction = null;
for (int i = 1; i <= 2; i++) {
try {
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Product product = new Product();
product.setId(1);
product.setName("Product " + i);
session.save(product);
transaction.commit();
} catch (Exception e) {
rollbackTransactionQuietly(transaction);
throw (e);
} finally {
closeSessionQuietly(session);
}
}
}
✅ 建议:在业务层做好校验,避免依赖数据库约束。
5.4. DataException
数据类型不匹配时抛出:
public void givenQueryWithDataTypeMismatch_WhenQueryExecuted_thenDataException() {
thrown.expectCause(isA(JdbcSQLDataException.class));
thrown.expectMessage("Data conversion error converting \"wrongTypeId\"");
Session session = sessionFactory.getCurrentSession();
NativeQuery<Product> query = session.createNativeQuery(
"select * from PRODUCT where id='wrongTypeId'", Product.class);
query.getResultList();
}
5.5. IdentifierGenerationException
主键生成失败时抛出:
@Test
public void givenEntityWithoutId_whenCallingSave_thenThrowIdentifierGenerationException() {
thrown.expect(isA(IdentifierGenerationException.class));
thrown.expectMessage("Identifier of entity must be manually assigned before calling 'persist()'");
Session session = null;
Transaction transaction = null;
try {
session = sessionFactory.openSession();
transaction = session.beginTransaction();
ProductEntity product = new ProductEntity();
product.setName("Product Name");
session.save(product);
transaction.commit();
} catch (Exception e) {
rollbackTransactionQuietly(transaction);
throw (e);
} finally {
closeSessionQuietly(session);
}
}
✅ 解决方案:
- 使用
@GeneratedValue
自动主键生成策略 - 或手动设置主键值
5.6. JDBCConnectionException
数据库连接失败时抛出,常见于网络问题或连接池配置错误。
✅ 排查建议:
- 检查数据库服务是否在线
- 检查连接池超时配置
- 检查用户名密码是否正确
5.7. QueryTimeoutException
查询超时或表空间不足时抛出。
✅ 解决方案:
- 设置查询超时时间
- 使用
@NamedQuery(timeout = 5000)
等方式
6. Session 状态相关异常
6.1. NonUniqueObjectException
同一 Session 中不能存在两个相同主键的对象:
public void givenSessionContainingAnId_whenIdAssociatedAgain_thenNonUniqueObjectException() {
thrown.expect(isA(NonUniqueObjectException.class));
thrown.expectMessage("A different object with the same identifier value was already associated with the session");
Session session = null;
Transaction transaction = null;
try {
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Product product = new Product();
product.setId(1);
product.setName("Product 1");
session.save(product);
product = new Product();
product.setId(1);
product.setName("Product 2");
session.save(product);
transaction.commit();
} catch (Exception e) {
rollbackTransactionQuietly(transaction);
throw (e);
} finally {
closeSessionQuietly(session);
}
}
✅ 解决方案:使用 merge()
方法代替 update()
。
6.2. StaleStateException
版本号或时间戳校验失败时抛出,常见于乐观锁场景:
@Test
public void whenUpdatingNonExistingObject_thenStaleStateException() {
thrown.expectCause(isA(StaleObjectStateException.class));
thrown.expectMessage("Row was updated or deleted by another transaction");
Session session = null;
Transaction transaction = null;
try {
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Product product1 = new Product();
product1.setId(15);
product1.setName("Product1");
session.update(product1);
transaction.commit();
} catch (Exception e) {
rollbackTransactionQuietly(transaction);
throw (e);
} finally {
closeSessionQuietly(session);
}
}
7. 懒加载异常
7.1. LazyInitializationException
Session 已关闭后访问懒加载属性时抛出:
✅ 解决方案:
- 使用
@Transactional
保持 Session 开启 - 使用
Open Session in View
(不推荐) - 使用
merge()
重新加载实体 - 使用
EntityGraph
预加载关联属性
7.2. 使用 EntityGraph 预加载懒属性
@Test
public void givenEnumParam_whenSettingEnumParam_thenSemanticExceptionIsNotThrown() {
Session session = sessionFactory.openSession();
assertDoesNotThrow(() -> session.createQuery("FROM User u WHERE u.role = ?1", User.class)
.setParameter(1, Role.ADMIN));
session.close();
}
8. 事务异常
@Test
public void givenTxnMarkedRollbackOnly_whenCommitted_thenTransactionException() {
thrown.expect(isA(IllegalStateException.class));
thrown.expectMessage("Transaction already active");
Session session = null;
Transaction transaction = null;
try {
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Product product1 = new Product();
product1.setId(15);
product1.setName("Product1");
session.save(product1);
transaction = session.beginTransaction();
transaction.setRollbackOnly();
transaction.commit();
} catch (Exception e) {
rollbackTransactionQuietly(transaction);
throw (e);
} finally {
closeSessionQuietly(session);
}
}
9. 并发异常
9.1. OptimisticLockException
并发更新时版本号冲突:
public void whenDeletingADeletedObject_thenOptimisticLockException() {
thrown.expect(isA(OptimisticLockException.class));
thrown.expectMessage("Row was updated or deleted by another transaction");
thrown.expectCause(isA(StaleStateException.class));
Session session = null;
Transaction transaction = null;
try {
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Product product = new Product();
product.setId(12);
product.setName("Product 12");
session.save(product1);
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
product = session.get(Product.class, 12);
session.createNativeQuery("delete from Product where id=12")
.executeUpdate();
session.delete(product);
transaction.commit();
} catch (Exception e) {
rollbackTransactionQuietly(transaction);
throw (e);
} finally {
closeSessionQuietly(session);
}
}
✅ 建议:
- 保持事务尽可能短
- 客户端频繁刷新数据
- 不要缓存实体对象
10. SemanticException
HQL/JPQL 语义错误时抛出:
@Test
public void givenEnumParam_whenExecutingQuery_thenThrowSemanticException() {
thrown.expectCause(isA(SemanticException.class));
thrown.expectMessage("Cannot compare left expression of type");
Session session = sessionFactory.openSession();
session.createQuery("FROM User u WHERE u.role = 'ADMIN'", User.class);
session.close();
}
✅ 解决方案:使用参数绑定而非字符串硬编码。
11. 总结
本文总结了 Hibernate 中常见的异常类型及其解决方案,涵盖映射、SQL 执行、事务、并发等多个方面。掌握这些异常的处理方式,有助于提升应用的健壮性和开发效率。
📌 完整代码可参考 GitHub 示例。