首页 > 解决方案 > 实体 id 存在,但在 equals() 方法中显示为 null(休眠)

问题描述

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

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。当我保存表单时,我必须检查是否删除了设置为与已保存 WorkPosition 相关的任何 WorkPosition 的 defaultCode 的 EmployeeCode。如果是这样,我重新分配它,否则我将无法删除 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 -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId()))并且它有效,但这似乎是错误的。为什么equals方法中的id为null?
我认为这可能是在持久性上下文中对实体的状态做一些事情,而 Hibernate 做了一些我不理解的事情。我使用这个答案的一些帮助来注销实体的状态:

标签: javahibernatejpaspring-data-jpa

解决方案


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

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

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

问题是 using@ManyToOne(fetch=lazy)和您当前在类中实现 equals的组合EmployeeCode......当您将ManyToOne关系声明为lazy,并加载包含/包装这种关系的实体时,hibernate 不会加载关系或实体,而是注入一个从您的Entity类扩展的代理类...代理类充当拦截器并仅在调用其声明的方法时才从持久层加载真实的实体数据...

这是棘手的部分:用于此类代理创建的库创建截获实体的精确副本,其中包括您在实体类中声明的相同实例变量(所有这些变量都使用默认 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);
   }
   ...
}

推荐阅读