实体 ID 存在但在 equals() 方法中显示为 null (Hibernate)
Entity id exists but is appears as null inside equals() method (Hibernate)
我有三个 classes:WorkPosition
、Employee
和 EmployeeCode
。 Employee 代表在某个地方工作的人,employee 在工作中可以有多个(工作)职位,employee's codes 代表员工(一个或多个代码)。对于每个 WorkPosition,必须分配一个默认的 EmployeeCode(字段 defaultCode
),如果有任何员工代码,则显示哪个代码代表该职位的员工。
Employee
-> WorkPosition
是一对多的关系
Employee
-> EmployeeCode
是一对多的关系
EmployeeCode
-> WorkPosition
是一对多的关系
WorkPosition
class:
@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
class:
@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
class:
@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 and/or 删除一个 EmployeeCode。当我保存表单时,我必须检查是否删除了一个 EmployeeCode,该 EmployeeCode 被设置为与保存的 WorkPosition 相关的任何 WorkPositions 的 defaultCode。如果是这样,我会重新分配它,否则我将无法删除 EmployeeCode,因为我会得到 ConstraintViolationException,因为 WorkPosition 仍会引用我希望删除的 EmployeeCode。
假设我有一个员工,有两个 EmployeeCodes(EC1 和 EC2)和两个 WorkPositions(WP1 和 WP2)。 WP1 的 DefaultCode 是 EC1,WP2 的 defaultCode 是 EC2。我保存了 WP1 的表格,但我没有删除任何东西。为了检查相关 WorkPosition (WP2) 的 defaultCode (EC2) 是否仍然存在,我遍历了所有剩余的代码(savedWorkPosition.getEmployeeCodes()
,其中 savedWorkPosition 等于 WP1)并检查它是否仍然包含 defaultCode(relatedWorkPosition.getDefaultCode()
其中从 db 查询 relatedWorkPosition 并且它引用 EC2).
newDefaultCode = savedWorkPosition.getEmployeeCodes() // [EC1, EC2]
.stream()
.filter(code -> code.equals(relatedWorkPosition.getDefaultCode()))
.findFirst()
.orElseGet(() -> ...);
然而,equals()
(查看上面的 EmployeeCode class)returns 为假。我在调试equals方法的时候,发现参数对象(EC2)的id是null
。当我在 equals 之前的过滤器调用中注销 id 时,我得到了正确的 id。我可以做 .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId()))
并且它有效,但这似乎是错误的。 为什么equals方法中的id为空?
我认为这可能是在持久性上下文中对实体的状态做一些事情,而 Hibernate 做了一些我不明白的事情。我使用了 this answer 的一些帮助来注销实体的状态:
entityManager.contains(relatedWorkPosition.getDefaultCode())
returns true
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(relatedWorkPosition.getDefaultCode())
returns 正确的 ID。
entityManager.contains(<any code in savedWorkPosition.getEmployeeCodes()>)
returns false
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(<any code in savedWorkPosition.getEmployeeCodes()>)
returns 正确的 ID。
Why is the id in the equals method null?
我会根据你之前说的回答(因为,我自己也经历过这样的事情):
However, the equals() (look at the EmployeeCode class above) returns false. When I debugged the equals method, I found out that the id of the parameter object (EC2) is null. When I log out the id in the filter call before the equals, I get the correct id. I could do .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId())) and it works, but this seems wrong ...
问题是结合使用 @ManyToOne(fetch=lazy)
和您当前在 class EmployeeCode
中实现的等号...当您将 ManyToOne
关系声明为lazy
,然后加载 contains/wraps 这种关系的实体,hibernate 不会加载关系或实体,而是注入 扩展 [=43= 的代理 class ] 从你的 Entity class ...代理 class 充当拦截器,仅当它的 声明的方法被调用...
这是棘手的部分:用于创建此类代理的库会创建截获实体的精确副本,其中包括相同的实例变量 您在实体 Class 中声明的(所有这些都使用默认 JAVA 值初始化)......当您将此类代理传递给 equals方法(公平地说,它可以是任何方法)并且该方法的逻辑访问提供的参数中的实例变量,您将访问代理的虚拟变量,而不是您 want/expect 的虚拟变量。这就是为什么您在 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对象是一个proxy/lazy实体......当你调用这样的方法时,代理的逻辑加载真实的实体并在其上调用真实的 equals
方法...代理 EmployeeCode class 的符号表示可能如下所示(注意,这不是真正的实现,只是一个示例更好地理解这个概念):
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);
}
...
}
我有三个 classes:WorkPosition
、Employee
和 EmployeeCode
。 Employee 代表在某个地方工作的人,employee 在工作中可以有多个(工作)职位,employee's codes 代表员工(一个或多个代码)。对于每个 WorkPosition,必须分配一个默认的 EmployeeCode(字段 defaultCode
),如果有任何员工代码,则显示哪个代码代表该职位的员工。
Employee
->WorkPosition
是一对多的关系Employee
->EmployeeCode
是一对多的关系EmployeeCode
->WorkPosition
是一对多的关系
WorkPosition
class:
@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
class:
@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
class:
@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 and/or 删除一个 EmployeeCode。当我保存表单时,我必须检查是否删除了一个 EmployeeCode,该 EmployeeCode 被设置为与保存的 WorkPosition 相关的任何 WorkPositions 的 defaultCode。如果是这样,我会重新分配它,否则我将无法删除 EmployeeCode,因为我会得到 ConstraintViolationException,因为 WorkPosition 仍会引用我希望删除的 EmployeeCode。
假设我有一个员工,有两个 EmployeeCodes(EC1 和 EC2)和两个 WorkPositions(WP1 和 WP2)。 WP1 的 DefaultCode 是 EC1,WP2 的 defaultCode 是 EC2。我保存了 WP1 的表格,但我没有删除任何东西。为了检查相关 WorkPosition (WP2) 的 defaultCode (EC2) 是否仍然存在,我遍历了所有剩余的代码(savedWorkPosition.getEmployeeCodes()
,其中 savedWorkPosition 等于 WP1)并检查它是否仍然包含 defaultCode(relatedWorkPosition.getDefaultCode()
其中从 db 查询 relatedWorkPosition 并且它引用 EC2).
newDefaultCode = savedWorkPosition.getEmployeeCodes() // [EC1, EC2]
.stream()
.filter(code -> code.equals(relatedWorkPosition.getDefaultCode()))
.findFirst()
.orElseGet(() -> ...);
然而,equals()
(查看上面的 EmployeeCode class)returns 为假。我在调试equals方法的时候,发现参数对象(EC2)的id是null
。当我在 equals 之前的过滤器调用中注销 id 时,我得到了正确的 id。我可以做 .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId()))
并且它有效,但这似乎是错误的。 为什么equals方法中的id为空?
我认为这可能是在持久性上下文中对实体的状态做一些事情,而 Hibernate 做了一些我不明白的事情。我使用了 this answer 的一些帮助来注销实体的状态:
entityManager.contains(relatedWorkPosition.getDefaultCode())
returnstrue
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(relatedWorkPosition.getDefaultCode())
returns 正确的 ID。entityManager.contains(<any code in savedWorkPosition.getEmployeeCodes()>)
returnsfalse
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(<any code in savedWorkPosition.getEmployeeCodes()>)
returns 正确的 ID。
Why is the id in the equals method null?
我会根据你之前说的回答(因为,我自己也经历过这样的事情):
However, the equals() (look at the EmployeeCode class above) returns false. When I debugged the equals method, I found out that the id of the parameter object (EC2) is null. When I log out the id in the filter call before the equals, I get the correct id. I could do .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId())) and it works, but this seems wrong ...
问题是结合使用 @ManyToOne(fetch=lazy)
和您当前在 class EmployeeCode
中实现的等号...当您将 ManyToOne
关系声明为lazy
,然后加载 contains/wraps 这种关系的实体,hibernate 不会加载关系或实体,而是注入 扩展 [=43= 的代理 class ] 从你的 Entity class ...代理 class 充当拦截器,仅当它的 声明的方法被调用...
这是棘手的部分:用于创建此类代理的库会创建截获实体的精确副本,其中包括相同的实例变量 您在实体 Class 中声明的(所有这些都使用默认 JAVA 值初始化)......当您将此类代理传递给 equals方法(公平地说,它可以是任何方法)并且该方法的逻辑访问提供的参数中的实例变量,您将访问代理的虚拟变量,而不是您 want/expect 的虚拟变量。这就是为什么您在 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对象是一个proxy/lazy实体......当你调用这样的方法时,代理的逻辑加载真实的实体并在其上调用真实的 equals
方法...代理 EmployeeCode class 的符号表示可能如下所示(注意,这不是真正的实现,只是一个示例更好地理解这个概念):
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);
}
...
}