在事务上下文中使用 equals() 方法进行比较时,单端关联在 Hibernate 中被破坏

Single-ended associations are broken in Hibernate, when compared using the equals() method within a transaction context

下面给出了国家与州之间的一对多关系(table 名称在数据库中使用 state_table,因为 state 可能是某些 RDBMS 中的保留字)。

下面给出了关联的反面(我假设拥有方不需要呈现)。

public class Country implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "country_id")
    private Long countryId;

    private static final long serialVersionUID = 1L;

    // Other fields + getters + setters + constructors.

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 47 * hash + Objects.hashCode(this.countryId);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {

        System.out.println("equals() in entity = " + (obj == null));
        System.out.println("this.countryId = " + this.countryId + " : other.countryId = " + ((Country) obj).countryId);

        if (obj == null) {
            return false;
        }

        if (getClass() != obj.getClass()) {
            return false;
        }

        final Country other = (Country) obj;        

        if (!Objects.equals(this.countryId, other.countryId)) {
            return false;
        }

        return true;
    }
}

Objects in the equals() and hashcode() methods is a utility class introduced in Java 7 holding only static utility methods .

以下代码段以上述实体class中的equals()方法为例

StateTable stateTable = entityManager.find(StateTable.class, 1L);
Country country = entityManager.find(Country.class, 1L);

stateTable.getCountryId().hashCode();

System.out.println("countryId = " + stateTable.getCountry().getCountryId());
System.out.println("equals = " + country.equals(stateTable.getCountry()));

第一个 stdout 语句显示正确的值 1 对应于实体的主键(countryId 类型 Long)。

然而,第二个 stdout 语句 returns false 令人难以置信,即使两个对象中的 countryId (主键)在获取时应该是相同的 - 它被破坏了.

实体 class 中 equals() 方法中的那两个标准输出语句输出以下内容。

equals() in entity = false
this.countryId = 1 : other.countryId = null

尽管所提供的国家/地区对象不是 null,正如 equals() 中第一个标准输出语句所确认的那样,((Country) obj).countryId returns null 令人惊讶,因此, equals() 方法 always returns false 不管提供什么对象作为参数,因为 Object 等于 null永远不是真的。 无论如何都不应该发生这种情况。


@ManyToOne 关联被延迟初始化 (fetch = FetchType.LAZY)。如果转向fetch = FetchType.EGAR,则一切正常。 equals() 方法 returns 基于提供的对象的正确结果。

代码在 Spring 服务中的事务下执行,由以下注释标记。

@Service
@Transactional(readOnly = true, propagation = Propagation.REQUIRED)

因此,应该不存在初始化惰性关联的问题。它在 JPA WikiBook.

中明确说明

For collection relationships sending size() is normally the best way to ensure a lazy relationship is instantiated. For OneToOne and ManyToOne relationships, normally just accessing the relationship is enough (i.e. employee.getAddress()), although for some JPA providers that use proxies you may need to send the object a message (i.e. employee.getAddress().hashCode()).

最后的陈述暗示了提供者特定的行为,

Although for some JPA providers that use proxies you may need to send the object a message (i.e. employee.getAddress().hashCode()).

然而,我已经尝试了两种方法,但都无济于事。

  1. 在将 stateTable.getCountry() 的值传递给 equals() 之前将其赋值给另一个变量。正如,

    Country anotherCountry = stateTable.getCountry();
    country.equals(anotherCountry); // Always returns false same as mentioned above.
    
  2. 使用 hashcode() 与此关联关联,使用 stateTable.getCountry().hashCode() 如前所述,以确保惰性关联在调用 equals() 之前在事务上下文中初始化,并且通过国家对象。

虽然我尝试使用 Hibernate 4.3.6 final (Tomcat 8.0.9.0),但此行为在 EclipseLink 2.6.0 (GlassFish 4.1) 上并不相同,它的行为很可能是直觉上预期的。

Hibernate 出了什么问题?解决方法是什么?

不要直接访问字段。而是使用 equals 方法中的吸气剂。

您必须了解代理的工作原理。代理派生自实际 class,但不保存其数据。从数据库加载代理时,会创建另一个 class(本例中为国家/地区)的实例,这是一个普通实例。它保存数据。代理将所有调用转发到此实例。

您的代码有两个潜在问题。

  • 字段不应该被另一个对象访问,因为它可能是一个代理。代理字段未初始化。调用 getter 并获得值。
  • .class returns 代理类型而非实际类型。不一定是Country。按照 the docs.
  • 中的建议使用 if ( !(other instanceof Cat) ) return false

请注意,由于代理将所有调用转发给真实对象,因此在您的实体中 this 永远不会引用代理。但是你得到的每一个参数都可能是一个代理。