【问题标题】:Entity id exists but is appears as null inside equals() method (Hibernate)实体 id 存在,但在 equals() 方法中显示为 null(休眠)
【发布时间】:2021-12-17 23:06:13
【问题描述】:

我有三个班级:WorkPositionEmployeeEmployeeCode。员工代表在某处工作的人,员工在工作中可以有很多(工作)职位,员工代码代表员工(一个或多个代码)。对于每个 WorkPosition,必须分配一个默认的 EmployeeCode(字段defaultCode),如果员工有任何代码,则显示哪个代码代表该职位的员工。

  • Employee -> WorkPosition 是一对多的关系
  • Employee -> EmployeeCode 是一对多的关系
  • EmployeeCode -> WorkPosition 是一对多的关系

WorkPosition类:

@Entity
@Table(name = "work_position")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class WorkPosition{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator")
    @SequenceGenerator(name = "sequence_generator")
    private Long id;

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @NotNull
    private Employee employee;

    @ManyToOne(fetch = FetchType.LAZY)
    private EmployeeCode defaultCode;

    // other fields, getters, setters, equals and hash ...

Employee类:

@Entity
@Table(name = "employee")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator")
    @SequenceGenerator(name = "sequence_generator")
    private Long id;

    @OneToMany(mappedBy = "employee", fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<EmployeeCode> employeeCodes;

    @OneToMany(mappedBy = "employee", fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<WorkPosition> workPositions;

    // other fields, getters, setters, equals and hash ...

EmployeeCode类:

@Entity
@Table(name = "employee_code")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EmployeeCode {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator")
    @SequenceGenerator(name = "sequence_generator")
    private Long id;

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @NotNull
    private Employee employee;

    @OneToMany(mappedBy = "defaultCode", fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<WorkPosition> defaultCodes;

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (!(o instanceof EmployeeCode)) {
            return false;
        } else {
            return this.id != null && this.id.equals(((EmployeeCode)o).id);
        }
    }

    // other fields, getters, setters, hash ...

因此,在我的示例中,一个员工的 WorkPositions 之间的唯一区别是 defaultCode,它可能在 WorkPositions 之间有所不同。
我有一个表单,我可以在其中操作与 WorkPosition 相关的所有数据。例如,我可以更改 WorkPosition 的 defaultCode 和/或删除 EmployeeCode。当我保存表单时,我必须检查是否删除了一个 EmployeeCode,该 EmployeeCode 设置为与保存的 WorkPosition 相关的任何 WorkPositions 的 defaultCode。如果是这样,我重新分配它,否则我将无法删除 EmployeeCode,因为我会得到一个 ConstraintViolationException,因为 WorkPosition 仍将引用我希望删除的 EmployeeCode。
假设我有一个员工,有两个员工代码(EC1 和 EC2)和两个工作岗位(WP1 和 WP2)。 WP1 的默认代码为 EC1,WP2 的默认代码为 EC2。我保存了 WP1 的表格,但我没有删除任何内容。为了检查相关 WorkPosition (WP2) 的 defaultCode (EC2) 是否仍然存在,我遍历所有剩余代码(savedWorkPosition.getEmployeeCodes(),其中 savedWorkPosition 等于 WP1)并检查它是否仍然包含 defaultCode(relatedWorkPosition.getDefaultCode(),其中relatedWorkPosition 是从 db 查询并引用 EC2)。

newDefaultCode = savedWorkPosition.getEmployeeCodes() // [EC1, EC2]
    .stream()
    .filter(code -> code.equals(relatedWorkPosition.getDefaultCode()))
    .findFirst()
    .orElseGet(() -> ...);

但是,equals()(查看上面的 EmployeeCode 类)返回 false。调试equals方法的时候发现参数对象(EC2)的id是null。当我在等号之前注销过滤器调用中的 id 时,我得到了正确的 id。我可以做.filter(code -&gt; code.getId().equals(relatedWorkPosition.getDefaultCode().getId())) 并且它有效,但这似乎是错误的。 为什么equals方法中的id为空?
我认为这可能是在持久性上下文中对实体的状态做一些事情,而 Hibernate 做了一些我不理解的事情。我使用了this answer 的一些帮助来注销实体的状态:

  • entityManager.contains(relatedWorkPosition.getDefaultCode()) 返回true
  • entityManagerFactory.getPersistenceUnitUtil().getIdentifier(relatedWorkPosition.getDefaultCode()) 返回正确的 id。
  • entityManager.contains(&lt;any code in savedWorkPosition.getEmployeeCodes()&gt;) 返回false
  • entityManagerFactory.getPersistenceUnitUtil().getIdentifier(&lt;any code in savedWorkPosition.getEmployeeCodes()&gt;) 返回正确的 id。

【问题讨论】:

  • 作为建议,不要在 @Entity 上实现 equals
  • 我看到你在集合上使用缓存。确保在添加/删除子实体时更新关系

标签: java hibernate jpa spring-data-jpa


【解决方案1】:

为什么equals方法中的id为空?

我会根据你之前所说的提供我的答案(因为我自己也经历过这样的事情):

但是,equals()(查看上面的 EmployeeCode 类)返回 false。调试equals方法时,发现参数对象(EC2)的id为null。当我在等号之前注销过滤器调用中的 id 时,我得到了正确的 id。我可以做 .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId())) 并且它有效,但这似乎是错误的......

问题是使用@ManyToOne(fetch=lazy) 和您当前在类EmployeeCode 中实现等号的组合...当您将ManyToOne 关系声明为lazy,并且您加载包含/的实体时包装这样的关系,hibernate 不加载关系或实体,而是从你的 Entity 类中注入一个 extends 的代理类......代理类充当拦截器并加载只有当它的一个声明的方法被调用时,来自持久层的真实实体数据...

这是棘手的部分:用于创建此类代理的库会创建截获实体的精确副本,其中包括您在其中声明的相同的实例变量您的实体类(所有这些都使用默认 JAVA 值初始化)...当您将此类代理传递给 equals 方法(公平地说,它可以是任何方法)并且该方法的逻辑访问实例变量时在提供的参数中,您将访问代理的虚拟变量,而不是您想要/期望的变量。这就是您在 equals 实现中看到这种奇怪行为的原因...

为了解决这个问题并避免错误,根据经验,我建议替换实例变量的使用并改为在提供的参数上调用 getter 和 setter 方法......在你的情况下,它将是这样的:

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (!(o instanceof EmployeeCode)) {
            return false;
        } else {
            return this.id != null 
                && this.id.equals(((EmployeeCode)o).getId());
        }
    }

你可能想知道为什么:

this.id != null && this.id.equals(((EmployeeCode)o).getId());

而不是:

this.getId() != null
    && this.getId().equals(((EmployeeCode)o).getId());

原因很简单:假设调用equals方法的java对象是一个代理/惰性实体......当你调用这样的方法时,代理的逻辑会加载真实的实体并调用真实的实体equals 方法...代理 EmployeeCode 类的符号表示可能如下所示(注意,它不是真正的实现,只是一个更好地理解概念的示例):

class EmployeeCodeProxy extends EmployeeCode {
   // same instance variables as EmployeeCode ...

   // the entity to be loaded ...
   private EmployeeCode $entity;
   ....
   public boolean equals(Object o) {
       if (this.$entity == null) {
           this.$entity = loadEntityFromPersistenceLayer();
       }
       return this.$entity.equals(o);
   }
   ...
}

【讨论】:

  • 用getter和setter替换实例变量解决了这个问题。谢谢你解释这背后的原因,我学到了很多!
猜你喜欢
  • 1970-01-01
  • 2019-02-06
  • 2012-09-09
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多