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 示例


原始标题:Common Hibernate Exceptions | Baeldung