我正在使用 Hibernate 和 JPA。 我有一个看起来像这样的实体:
@Data
@Entity
@Table(name = "MY_ENTITY")
public class MyEntity {
@Id
@Column(name = "ID")
public Long id;
@Version
@Column(name = "VERSION")
public Long version = 1L;
@Embedded
@AttributeOverride(name = "timestamp", column = @Column(name = "UPDATED_TIME"))
public CustomTimestamp updatedTime;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "ENTITY_CONNECTION", joinColumns = @JoinColumn(name = "ENTITY_ID"))
@OrderColumn(name = "CONNECTION_ORDER")
@Fetch(FetchMode.SUBSELECT)
@JsonDeserialize(using = ConnectionDeserializer.class)
public List<Connection> connections = new ArrayList<>();
@PreUpdate
public void preUpdate() {
this.updatedTime = new CustomTimestamp();
}
}
@Data
@Embeddable
public class Connection {
@Column(name = "CONNECTED_ENTITY_ID")
public String connectedEntityId;
@Column(name = "CONNECTED_ENTITY_TYPE")
public String connectedEntityType;
@Column(name = "NOTE")
public String note;
}
实体的输入包括带有删除标志的所有字段。提到的反序列化器获取现有列表并与发送的更新进行比较。更新列表中的现有实体,添加新实体并删除标有“删除”标志的实体。
在 API 中,我公开了“修补”实体的路径,这意味着用户传递他们想要更新的字段。我执行以下逻辑:
MyEntity originalEntity = repository.findById(id);
ObjectReader entityForUpdate = objectMapper.readerForUpdating(originalEntity);
MyEntity updatedEntity = objectMapper.update(entityForUpdate, updateInput);
repository.saveAndFlush(updatedEntity);
示例“补丁”输入类似于:
patch /entity/{id}
body:
{
"connections": [
{
"connectedEntityId": "test",
"connectedEntityType": "testType"
}
]
}
发送两次时,我希望第二次不会检测到任何“脏”内容,因此不会增加版本,但确实如此。
查看 Hibernate 的日志时,看起来“连接”集合很脏。
Hibernate的脏检查是如何判断集合是否脏的?
尝试使用 Set 而不是 ArrayList - Collection 仍然很脏。
尝试使用 OneToMany 映射 - 问题是相反的 - 父实体永远不会更新。据我了解,这不是最好的选择,因为“连接”本身并不作为一个实体。
尝试将生成的 ID 添加到连接实体 - 集合仍然脏。
已验证所有实体均按预期实施
equals()
Hibernate ORM 不会检查集合元素是否深度等于初始状态的元素。 为了避免不必要的更新语句,您必须避免在这种情况下替换对象。
使用 ObjectMapper,我的解决方案是重写默认设置器,如下所示:
public void setConnections(List<Connection> connections) {
if (!CollectionUtils.isEqualCollection(this.connections, connections)) {
this.connections.clear();
this.connections.addAll(connections);
}
}