Common Hibernate Exceptions – 常见的Hibernate例外情况

最后修改: 2019年 1月 20日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this tutorial, we’ll discuss some common exceptions we can encounter while working with Hibernate.

在本教程中,我们将讨论在使用Hibernate时可能遇到的一些常见异常。

We’ll review their purpose and some common causes. Additionally, we’ll look into their solutions.

我们将回顾其目的和一些常见的原因。此外,我们还将研究其解决方案。

2. Hibernate Exception Overview

2.Hibernate 异常概述

Many conditions can cause exceptions to be thrown while using Hibernate. These can be mapping errors, infrastructure problems, SQL errors, data integrity violations, session problems, and transaction errors.

在使用Hibernate时,许多情况都会导致抛出异常。这些可能是映射错误、基础设施问题、SQL错误、数据完整性违反、会话问题和事务错误。

These exceptions mostly extend from HibernateException. However, if we’re using Hibernate as a JPA persistence provider, these exceptions may get wrapped into PersistenceException.

这些异常大多是从HibernateException延伸而来。但是,如果我们使用Hibernate作为JPA持久化提供者,这些异常可能会被包装成PersistenceException

Both of these base classes extend from RuntimeException. Therefore, they’re all unchecked. Hence, we don’t need to catch or declare them at every place they’re used.

这两个基类都是从RuntimeException延伸而来。因此,它们都是未经检查的。因此,我们不需要在每个使用它们的地方捕捉或声明它们。

Furthermore, most of these are unrecoverable. As a result, retrying the operation would not help. This means we have to abandon the current session on encountering them.

此外,其中大部分都是无法恢复的。因此,重试操作也无济于事。这意味着我们必须在遇到这些问题时放弃当前会话。

Let’s now look into each of these, one at a time.

现在让我们逐一来看看这些。

3. Mapping Errors

3.制图错误

Object-Relational mapping is a major benefit of Hibernate. Specifically, it frees us from manually writing SQL statements.

对象-关系映射是Hibernate的一个主要优点。具体来说,它将我们从手动编写SQL语句中解放出来。

At the same time, it requires us to specify the mapping between Java objects and database tables. Accordingly, we specify them using annotations or through mapping documents. These mappings can be coded manually. Alternatively, we can use tools to generate them.

同时,它要求我们指定Java对象和数据库表之间的映射。因此,我们使用注解或通过映射文件来指定它们。这些映射可以是手动编码的。或者,我们也可以使用工具来生成它们。

While specifying these mappings, we may make mistakes. These could be in the mapping specification. Or, there can be a mismatch between a Java object and the corresponding database table.

在指定这些映射的时候,我们可能会犯错误。这些可能是在映射规范中。或者,在一个Java对象和相应的数据库表之间可能会出现不匹配。

Such mapping errors generate exceptions. We come across them frequently during initial development. Additionally, we may run into them while migrating changes across environments.

这种映射错误会产生异常。在最初的开发过程中,我们经常会遇到这种情况。此外,我们还可能在跨环境迁移变更时遇到这些错误。

Let’s look into these errors with some examples.

让我们通过一些例子来了解一下这些错误。

3.1. MappingException

3.1.MappingException

A problem with the object-relational mapping causes a MappingException to be thrown:

对象-关系映射的问题导致抛出MappingException

public void whenQueryExecutedWithUnmappedEntity_thenMappingException() {
    thrown.expectCause(isA(MappingException.class));
    thrown.expectMessage("Unknown entity: java.lang.String");

    Session session = sessionFactory.getCurrentSession();
    NativeQuery<String> query = session
      .createNativeQuery("select name from PRODUCT", String.class);
    query.getResultList();
}

In the above code, the createNativeQuery method tries to map the query result to the specified Java type String. It uses the implicit mapping of the String class from Metamodel to do the mapping.

在上述代码中,createNativeQuery方法试图将查询结果映射到指定的Java类型String。它使用String类的隐式映射,从Metamodel来进行映射。

However, the String class doesn’t have any mapping specified. Therefore, Hibernate doesn’t know how to map the name column to String and throws the exception.

然而,String类并没有指定任何映射。因此,Hibernate不知道如何将name列映射到String,并抛出了这个异常。

For a detailed analysis of possible causes and solutions, check out Hibernate Mapping Exception – Unknown Entity.

关于可能的原因和解决方案的详细分析,请查看Hibernate Mapping Exception – Unknown Entity

Similarly, other errors can also cause this exception:

同样,其他错误也会导致这种异常。

  • Mixing annotations on fields and methods
  • Failing to specify the @JoinTable for a @ManyToMany association
  • The default constructor of the mapped class throws an exception during mapping processing

Furthermore, MappingException has a few subclasses which can indicate specific mapping problems:

此外,MappingException有几个子类,可以表示具体的映射问题:

  • AnnotationException – a problem with an annotation
  • DuplicateMappingException – duplicate mapping for a class, table, or property name
  • InvalidMappingException – mapping is invalid
  • MappingNotFoundException – mapping resource could not be found
  • PropertyNotFoundException – an expected getter or setter method could not be found on a class

Therefore, if we come across this exception, we should first verify our mappings.

因此,如果我们遇到这种异常,我们应该首先验证我们的映射

3.2. AnnotationException

3.2.AnnotationException

To understand the AnnotationException, let’s create an entity without an identifier annotation on any field or property:

为了理解AnnotationException,让我们创建一个在任何字段或属性上没有标识符注释的实体。

@Entity
public class EntityWithNoId {
    private int id;
    public int getId() {
        return id;
    }

    // standard setter
}

Since Hibernate expects every entity to have an identifier, we’ll get an AnnotationException when we use the entity:

由于Hibernate期望每个实体都有一个标识符,我们在使用实体时将得到一个AnnotationException

public void givenEntityWithoutId_whenSessionFactoryCreated_thenAnnotationException() {
    thrown.expect(AnnotationException.class);
    thrown.expectMessage("No identifier specified for entity");

    Configuration cfg = getConfiguration();
    cfg.addAnnotatedClass(EntityWithNoId.class);
    cfg.buildSessionFactory();
}

Furthermore, some other probable causes are:

此外,其他一些可能的原因是。

  • Unknown sequence generator used in the @GeneratedValue annotation
  • @Temporal annotation used with a Java 8 Date/Time class
  • Target entity missing or non-existent for @ManyToOne or @OneToMany
  • Raw collection classes used with relationship annotations @OneToMany or @ManyToMany
  • Concrete classes used with the collection annotations @OneToMany, @ManyToMany or @ElementCollection as Hibernate expects the collection interfaces

To resolve this exception, we should first check the specific annotation mentioned in the error message.

为了解决这个异常,我们应该首先检查错误信息中提到的具体注释。

3.3. QuerySyntaxException

3.3QuerySyntaxException

Before diving deep into the details, let’s try to understand what the exception means.

在深入研究细节之前,让我们试着了解一下这个例外的含义。

QuerySyntaxException, as the name indicates, tells us that the specified query has invalid syntax.

QuerySyntaxException,如其名所示,告诉我们指定的查询有无效的语法

The most typical cause of this exception is using the table name instead of the class name in HQL queries.

造成这种异常的最典型原因是在HQL查询中使用了表名而不是类名

For instance, let’s consider the Product entity:

例如,让我们考虑一下Product实体。

import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "PRODUCT")
public class Product {

    private int id;
    private String name;
    private String description;

    // Getters and setters
}

@Entity denotes that the annotated class is an entity. It tells us that this class represents a table stored in a database.

@Entity表示被注释的类是一个entity。它告诉我们,这个类代表了存储在数据库中的一个表。

Typically, the table name may be different from the entity name. So, that’s where @Table comes to the rescue. It allows us to specify the exact name of the table in the database.

通常情况下,表名可能与实体名不同。所以,这就是@Table的用武之地。它允许我们指定数据库中的表的确切名称。

Now, let’s exemplify the exception using a test case:

现在,让我们用一个测试案例来例证这个异常。

@Test
public void whenQueryExecutedWithInvalidClassName_thenQuerySyntaxException() {
    thrown.expectCause(isA(QuerySyntaxException.class));
    thrown.expectMessage("PRODUCT is not mapped [from PRODUCT]");

    Session session = sessionFactory.openSession();
    List<Product> result = session.createQuery("from PRODUCT", Product.class)
        .getResultList();
}

As we can see, Hibernate fails with QuerySyntaxException because we used PRODUCT instead of Product in our query. In other words, we must use the entity name and not the table name in our query.

我们可以看到,Hibernate以QuerySyntaxException失败,因为我们在查询中使用了PRODUCT而不是Product换句话说,我们必须在查询中使用实体名称而不是表的名称。

4. Schema Management Errors

4.计划管理失误

Automatic database schema management is another benefit of Hibernate. For example, it can generate DDL statements to create or validate database objects.

自动数据库模式管理是Hibernate的另一个好处。例如,它可以生成DDL语句来创建或验证数据库对象。

To use this feature, we need to set the hibernate.hbm2ddl.auto property appropriately.

为了使用这个功能,我们需要适当地设置hibernate.hbm2ddl.auto属性。

If there are problems while performing schema management, we get an exception. Let’s examine these errors.

如果在执行模式管理时出现问题,我们会得到一个异常。让我们研究一下这些错误。

4.1. SchemaManagementException

4.1.SchemaManagementException

Any infrastructure-related problem in performing schema management causes a SchemaManagementException.

在执行模式管理时,任何与基础设施相关的问题都会导致SchemaManagementException

To demonstrate, let’s instruct Hibernate to validate the database schema:

为了演示,让我们指示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();
}

Since the table corresponding to Product is not present in the database, we get the schema-validation exception while building the SessionFactory.

由于数据库中没有对应于Product的表,我们在构建SessionFactory时得到了模式验证异常。

Additionally, there are other possible scenarios for this exception:

此外,这种例外还有其他可能的情况。

  • unable to connect to the database to perform schema management tasks
  • the schema is not present in the database

4.2. CommandAcceptanceException

4.2.CommandAcceptanceException

Any problem executing a DDL corresponding to a specific schema management command can cause a CommandAcceptanceException.

执行与特定模式管理命令相对应的DDL的任何问题都会导致一个CommandAcceptanceException

As an example, let’s specify the wrong dialect while setting up the SessionFactory:

作为一个例子,让我们在设置SessionFactory时指定错误的方言。

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.getProperties()
      .put(AvailableSettings.HBM2DDL_HALT_ON_ERROR, true);

    cfg.addAnnotatedClass(Product.class);
    cfg.buildSessionFactory();
}

Here, we’ve specified the wrong dialect: MySQLDialect. Also, we’re instructing Hibernate to update the schema objects. Consequently, the DDL statements executed by Hibernate to update the H2 database will fail and we’ll get an exception.

这里,我们指定了错误的方言。MySQLDialect。此外,我们还指示Hibernate更新模式对象。因此,Hibernate为更新H2数据库而执行的DDL语句将失败,我们将得到一个异常。

By default, Hibernate silently logs this exception and moves on. When we later use the SessionFactory, we get the exception.

默认情况下,Hibernate默默地记录了这个异常并继续前进。当我们后来使用SessionFactory时,我们得到了这个异常。

To ensure that an exception is thrown on this error, we’ve set the property HBM2DDL_HALT_ON_ERROR to true.

为了确保在这个错误中抛出一个异常,我们将属性HBM2DDL_HALT_ON_ERROR设置为true

Similarly, these are some other common causes for this error:

同样地,这些也是造成这个错误的一些其他常见原因。

  • There is a mismatch in column names between mapping and the database
  • Two classes are mapped to the same table
  • The name used for a class or table is a reserved word in the database, like USER, for example
  • The user used to connect to the database does not have the required privilege

5. SQL Execution Errors

5.SQL执行错误

When we insert, update, delete or query data using Hibernate, it executes DML statements against the database using JDBC. This API raises an SQLException if the operation results in errors or warnings.

当我们使用Hibernate插入、更新、删除或查询数据时,它使用JDBC对数据库执行DML语句。如果操作导致错误或警告,该API会引发一个SQLException

Hibernate converts this exception into JDBCException or one of its suitable subclasses:

Hibernate将此异常转换为JDBCException或其合适的子类之一。

  • ConstraintViolationException
  • DataException
  • JDBCConnectionException
  • LockAcquisitionException
  • PessimisticLockException
  • QueryTimeoutException
  • SQLGrammarException
  • GenericJDBCException

Let’s discuss common errors.

我们来讨论一下常见的错误。

5.1. JDBCException

5.1 JDBCException

JDBCException is always caused by a particular SQL statement. We can call the getSQL method to get the offending SQL statement.

JDBCException总是由一个特定的SQL语句引起。我们可以调用getSQL方法来获取违规的SQL语句。

Furthermore, we can retrieve the underlying SQLException with the getSQLException method.

此外,我们可以通过getSQLException方法检索底层的SQLException

5.2. SQLGrammarException

5.2.SQLGrammarException

SQLGrammarException indicates that the SQL sent to the database was invalid. It could be due to a syntax error or an invalid object reference.

SQLGrammarException表明发送到数据库的SQL是无效的。这可能是由于语法错误或无效的对象引用。

For example, a missing table can result in this error while querying data:

例如,在查询数据时,一个缺失的表会导致这个错误

public void givenMissingTable_whenQueryExecuted_thenSQLGrammarException() {
    thrown.expect(isA(PersistenceException.class));
    thrown.expectCause(isA(SQLGrammarException.class));
    thrown.expectMessage("SQLGrammarException: could not prepare statement");

    Session session = sessionFactory.getCurrentSession();
    NativeQuery<Product> query = session.createNativeQuery(
      "select * from NON_EXISTING_TABLE", Product.class);
    query.getResultList();
}

Also, we can get this error while saving data if the table is missing:

另外,如果表格丢失,我们在保存数据时也会得到这个错误。

public void givenMissingTable_whenEntitySaved_thenSQLGrammarException() {
    thrown.expect(isA(PersistenceException.class));
    thrown.expectCause(isA(SQLGrammarException.class));
    thrown
      .expectMessage("SQLGrammarException: could not prepare statement");

    Configuration cfg = getConfiguration();
    cfg.addAnnotatedClass(Product.class);

    SessionFactory sessionFactory = cfg.buildSessionFactory();
    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);
        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
        closeSessionFactoryQuietly(sessionFactory);
    }
}

Some other possible causes are:

其他一些可能的原因是。

  • The naming strategy used doesn’t map the classes to the correct tables
  • The column specified in @JoinColumn doesn’t exist

5.3. ConstraintViolationException

5.3.ConstraintViolationException

A ConstraintViolationException indicates that the requested DML operation caused an integrity constraint to be violated. We can get the name of this constraint by calling the getConstraintName method.

ConstraintViolationException表示请求的DML操作导致完整性约束被违反。我们可以通过调用getConstraintName方法获得这个约束的名称。

A common cause of this exception is trying to save duplicate records:

造成这种异常的一个常见原因是试图保存重复的记录:

public void whenDuplicateIdSaved_thenConstraintViolationException() {
    thrown.expect(isA(PersistenceException.class));
    thrown.expectCause(isA(ConstraintViolationException.class));
    thrown.expectMessage(
      "ConstraintViolationException: 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);
        }
    }
}

Also, saving a null value to a NOT NULL column in the database can raise this error.

另外,将一个null值保存到数据库中的NOT NULL列中也会引起这个错误。

In order to resolve this error, we should perform all validations in the business layer. Furthermore, database constraints should not be used to do application validations.

为了解决这个错误,我们应该在业务层执行所有的验证。此外,数据库约束不应该被用来做应用验证。

5.4. DataException

5.4.DataException

DataException indicates that the evaluation of an SQL statement resulted in some illegal operation, type mismatch or incorrect cardinality.

DataException表示对一个SQL语句的评估导致了一些非法操作、类型不匹配或不正确的cardinality。

For instance, using character data against a numeric column can cause this error:

例如,针对数字列使用字符数据会导致这个错误。

public void givenQueryWithDataTypeMismatch_WhenQueryExecuted_thenDataException() {
    thrown.expectCause(isA(DataException.class));
    thrown.expectMessage(
      "org.hibernate.exception.DataException: could not prepare statement");

    Session session = sessionFactory.getCurrentSession();
    NativeQuery<Product> query = session.createNativeQuery(
      "select * from PRODUCT where id='wrongTypeId'", Product.class);
    query.getResultList();
}

To fix this error, we should ensure that the data types and length match between the application code and the database.

为了解决这个错误,我们应该确保应用程序代码和数据库之间的数据类型和长度相匹配

5.5. JDBCConnectionException

5.5.JDBCConnectionException

A JDBCConectionException indicates problems communicating with the database.

JDBCConectionException表明与数据库的通信有问题。

For example, a database or network going down can cause this exception to be thrown.

例如,数据库或网络瘫痪会导致这个异常被抛出。

Additionally, an incorrect database setup can cause this exception. One such case is the database connection being closed by the server because it was idle for a long time. This can happen if we’re using connection pooling and the idle timeout setting on the pool is more than the connection timeout value in the database.

此外,一个不正确的数据库设置也会导致这种异常。其中一种情况是数据库连接因长时间闲置而被服务器关闭。如果我们使用连接池,并且连接池上的空闲超时设置超过了数据库中的连接超时值,就会发生这种情况。

To solve this problem, we should first ensure that the database host is present and that it’s up. Then, we should verify that the correct authentication is used for the database connection. Finally, we should check that the timeout value is correctly set on the connection pool.

为了解决这个问题,我们首先应该确保数据库主机是存在的,并且它是正常的。然后,我们应该验证数据库连接是否使用了正确的认证。最后,我们应该检查连接池上的超时值是否正确设置。

5.6. QueryTimeoutException

5.6.QueryTimeoutException

When a database query times out, we get this exception. We can also see it due to other errors, such as the tablespace becoming full.

当一个数据库查询超时时,我们会得到这个异常。我们也可以看到它是由于其他错误引起的,比如表空间变满。

This is one of the few recoverable errors, which means that we can retry the statement in the same transaction.

这是少数可恢复的错误之一,这意味着我们可以在同一个事务中重试该语句。

To fix this issue, we can increase the query timeout for long-running queries in multiple ways:

为了解决这个问题,我们可以通过多种方式增加长期运行的查询超时

  • Set the timeout element in a @NamedQuery or @NamedNativeQuery annotation
  • Invoke the setHint method of the Query interface
  • Call the setTimeout method of the Transaction interface
  • Invoke the setTimeout method of the Query interface

6. Session-State-Related Errors

6.会话状态相关的错误

Let’s now look into errors due to Hibernate session usage errors.

现在我们来看看由于Hibernate会话使用错误造成的错误。

6.1. NonUniqueObjectException

6.1. NonUniqueObjectException

Hibernate doesn’t allow two objects with the same identifier in a single session.

Hibernate不允许在一个会话中有两个具有相同标识符的对象。

If we try to associate two instances of the same Java class with the same identifier in a single session, we get a NonUniqueObjectException. We can get the name and identifier of the entity by calling the getEntityName() and getIdentifier() methods.

如果我们试图在一个会话中把同一个Java类的两个实例与同一个标识符联系起来,我们会得到一个NonUniqueObjectException。我们可以通过调用getEntityName()getIdentifier()方法获得实体的名称和标识符。

To reproduce this error, let’s try to save two instances of Product with the same id with a session:

为了重现这个错误,让我们尝试用一个会话保存两个具有相同id的Product的实例。

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);
    }
}

We’ll get a NonUniqueObjectException, as expected.

我们会得到一个NonUniqueObjectException,如预期。

This exception occurs frequently while reattaching a detached object with a session by calling the update method. If the session has another instance with the same identifier loaded, then we get this error. In order to fix this, we can use the merge method to reattach the detached object.

在通过调用update方法将分离的对象与会话重新连接时,这个异常经常发生。如果会话有另一个加载了相同标识符的实例,那么我们就会得到这个错误。为了解决这个问题,我们可以使用merge方法来重新连接分离的对象。

6.2. StaleStateException

6.2.StaleStateException

Hibernate throws StaleStateExceptions when the version number or timestamp check fails. It indicates that the session contained stale data.

当版本号或时间戳检查失败时,Hibernate会抛出StaleStateExceptions。它表明会话包含陈旧的数据。

Sometimes this gets wrapped into an OptimisticLockException.

有时这会被包裹成一个OptimisticLockException

This error usually happens while using long-running transactions with versioning.

这个错误通常发生在使用带有版本控制的长期运行的事务中。

In addition, it can also happen while trying to update or delete an entity if the corresponding database row doesn’t exist:

此外,如果相应的数据库行不存在,在试图更新或删除一个实体时也会发生这种情况。

public void whenUpdatingNonExistingObject_thenStaleStateException() {
    thrown.expect(isA(OptimisticLockException.class));
    thrown.expectMessage(
      "Batch update returned unexpected row count from update");
    thrown.expectCause(isA(StaleStateException.class));

    Session session = null;
    Transaction transaction = null;

    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Product product = new Product();
        product.setId(15);
        product.setName("Product1");
        session.update(product);
        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

Some other possible scenarios are:

其他一些可能的情况是。

  • we did not specify a proper unsaved-value strategy for the entity
  • two users tried to delete the same row at almost the same time
  • we manually set a value in the autogenerated ID or version field

7. Lazy Initialization Errors

7.懒惰的初始化错误

We usually configure associations to be loaded lazily in order to improve application performance. The associations are fetched only when they’re first used.

我们通常将关联配置为懒散地加载,以提高应用程序的性能。联想只有在第一次使用时才会被取走。

However, Hibernate requires an active session to fetch data. If the session is already closed when we try to access an uninitialized association, we get an exception.

然而,Hibernate需要一个活动的会话来获取数据。如果当我们试图访问一个未初始化的关联时,会话已经关闭,我们会得到一个异常。

Let’s look into this exception and the various ways to fix it.

让我们来研究一下这种异常情况以及解决它的各种方法。

7.1. LazyInitializationException

7.1.LazyInitializationException

LazyInitializationException indicates an attempt to load uninitialized data outside an active session. We can get this error in many scenarios.

LazyInitializationException表示试图在活动会话之外加载未初始化的数据。我们可以在许多情况下得到这个错误。

First, we can get this exception while accessing a lazy relationship in the presentation layer. The reason is that the entity was partially loaded in the business layer and the session was closed.

首先,我们可以在访问表现层中的懒惰关系时得到这个异常。原因是实体在业务层中被部分加载,并且会话被关闭。

Secondly, we can get this error with Spring Data if we use the getOne method. This method lazily fetches the instance.

其次,如果我们使用getOne方法,我们可以在Spring Data中获得这个错误。这个方法懒洋洋地获取实例。

There are many ways to solve this exception.

有许多方法来解决这个例外。

First of all, we can make all relationships eagerly loaded. But, this would impact the application performance because we’ll be loading data that won’t be used.

首先,我们可以让所有的关系急于加载。但是,这将影响应用程序的性能,因为我们将加载不会被使用的数据。

Secondly, we can keep the session open until the view is rendered. This is known as the “Open Session in View” and it’s an anti-pattern. We should avoid this as it has several disadvantages.

其次,我们可以保持会话的开放,直到视图被渲染。这被称为”在视图中打开会话“,这是个反模式的做法。我们应该避免这种做法,因为它有几个缺点。

Thirdly, we can open another session and reattach the entity in order to fetch the relationships. We can do so by using the merge method on the session.

第三,我们可以打开另一个会话并重新连接实体,以便获取关系。我们可以通过在会话上使用merge方法来这样做。

Finally, we can initialize the required associations in the business layers. We’ll discuss this in the next section.

最后,我们可以在业务层中初始化所需的关联。我们将在下一节讨论这个问题。

7.2. Initializing Relevant Lazy Relationships in the Business Layer

7.2.在业务层中初始化相关的懒惰关系

There are many ways to initialize lazy relationships.

有许多方法来初始化懒惰关系。

One option is to initialize them by invoking the corresponding methods on the entity. In this case, Hibernate will issue multiple database queries causing degraded performance. We refer to it as the “N+1 SELECT” problem.

一种选择是通过调用实体上的相应方法来初始化它们。在这种情况下,Hibernate会发出多个数据库查询,导致性能下降。我们把它称为 “N+1 SELECT “问题。

Secondly, we can use Fetch Join to get the data in a single query. However, we need to write custom code to achieve this.

其次,我们可以使用Fetch Join,在一次查询中获得数据。然而,我们需要编写自定义代码来实现这一点。

Finally, we can use entity graphs to define all the attributes to be fetched. We can use the annotations @NamedEntityGraph, @NamedAttributeNode, and @NamedEntitySubgraph to declaratively define the entity graph. We can also define them programmatically with the JPA API. Then, we retrieve the entire graph in a single call by specifying it in the fetch operation.

最后,我们可以使用entity graphs来定义要获取的所有属性。我们可以使用注解@NamedEntityGraph、@NamedAttributeNode@NamedEntitySubgraph来声明式地定义实体图。我们也可以通过JPA API以编程方式定义它们。然后,我们通过在fetch操作中指定它,在一次调用中检索整个图

8. Transaction Issues

8.事务问题

Transactions define units of work and isolation between concurrent activities. We can demarcate them in two different ways. First, we can define them declaratively using annotations. Second, we can manage them programmatically using the Hibernate Transaction API.

交易定义了工作单位和同时进行的活动之间的隔离。我们可以通过两种不同的方式来划定它们。首先,我们可以使用注解来声明性地定义它们。其次,我们可以使用Hibernate Transaction API以编程方式管理它们。

Furthermore, Hibernate delegates the transaction management to a transaction manager. If a transaction could not be started, committed or rolled back due to any reason, Hibernate throws an exception.

此外,Hibernate将事务管理委托给一个事务管理器。如果一个事务由于任何原因不能被启动、提交或回滚,Hibernate会抛出一个异常。

We usually get a TransactionException or an IllegalArgumentException depending on the transaction manager.

我们通常会得到一个TransactionException或一个IllegalArgumentException,这取决于事务管理器。

As an illustration, let’s try to commit a transaction which has been marked for rollback:

作为一个例子,让我们尝试提交一个已经被标记为回滚的事务。

public void 
givenTxnMarkedRollbackOnly_whenCommitted_thenTransactionException() {
    thrown.expect(isA(TransactionException.class));
    thrown.expectMessage(
        "Transaction was marked for rollback only; cannot commit");

    Session session = null;
    Transaction transaction = null;
    try {
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();

        Product product = new Product();
        product.setId(15);
        product.setName("Product1");
        session.save(product);
        transaction.setRollbackOnly();

        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

Similarly, other errors can also cause an exception:

同样地,其他错误也会导致异常。

  • Mixing declarative and programmatic transactions
  • Attempting to start a transaction when another one is already active in the session
  • Trying to commit or rollback without starting a transaction
  • Trying to commit or rollback a transaction multiple times

9. Concurrency Issues

9.并发问题

Hibernate supports two locking strategies to prevent database inconsistency due to concurrent transactions – optimistic and pessimistic. Both of them raise an exception in case of a locking conflict.

Hibernate支持两种锁定策略,以防止因并发事务而导致的数据库不一致 – optimisticpessimistic。这两种方法在发生锁定冲突的情况下都会引发一个异常。

To support high concurrency and high scalability, we typically use optimistic concurrency control with version checking. This uses version numbers or timestamps to detect conflicting updates.

为了支持高并发性和高可扩展性,我们通常使用乐观的并发控制与版本检查。这使用版本号或时间戳来检测冲突的更新。

OptimisticLockingException is thrown to indicate an optimistic locking conflict. For instance, we get this error if we perform two updates or deletes of the same entity without refreshing it after the first operation:

OptimisticLockingException被抛出,表明存在乐观的锁定冲突。例如,如果我们对同一个实体进行两次更新或删除,而在第一次操作后没有刷新它,我们会得到这个错误。

public void whenDeletingADeletedObject_thenOptimisticLockException() {
    thrown.expect(isA(OptimisticLockException.class));
    thrown.expectMessage(
        "Batch update returned unexpected row count from update");
    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();
        // We need to refresh to fix the error.
        // session.refresh(product);
        session.delete(product);
        transaction.commit();
    } catch (Exception e) {
        rollbackTransactionQuietly(transaction);
        throw (e);
    } finally {
        closeSessionQuietly(session);
    }
}

Likewise, we can also get this error if two users try to update the same entity at almost the same time. In this case, the first may succeed and the second raises this error.

同样地,如果两个用户几乎同时试图更新同一个实体,我们也会得到这个错误。在这种情况下,第一个人可能会成功,而第二个人则会引发这个错误。

Therefore, we cannot completely avoid this error without introducing pessimistic locking. However, we can minimize the probability of its occurrence by doing the following:

因此,如果不引入悲观的锁定,我们无法完全避免这种错误。然而,我们可以通过以下方式将其发生的概率降到最低。

  • Keep update operations as short as possible
  • Update entity representations in the client as often as possible
  • Do not cache the entity or any value object representing it
  • Always refresh the entity representation on the client after update

10. Conclusion

10.结语

In this article, we looked into some common exceptions encountered while using Hibernate. Furthermore, we investigated their probable causes and resolutions.

在这篇文章中,我们研究了一些在使用Hibernate时遇到的常见异常。此外,我们还调查了它们的可能原因和解决办法。

As usual, the full source code can be found over on GitHub.

像往常一样,完整的源代码可以在GitHub上找到超过