如何解决为什么休眠状态在保存子级时需要保存父级,并且即使父级没有变化也导致OptimisticLockException? 说明潜在问题 ORM和实体映射用于策略性地检索数据不应将它们用于实际定义对象关系
我们正在尝试在短时间内节省许多孩子,并且休眠状态会继续提供OptimisticLockException。 这里是这种情况的一个简单例子:
University
id
name
audit_version
Student
id
name
university_id
audit_version
其中university_id可以为null。
java对象如下:
@Entity
@Table(name = "university")
@DynamicUpdate
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class University {
@Id
@SequenceGenerator(name = "university_id_sequence_generator",sequenceName = "university_id_sequence",allocationSize = 1)
@GeneratedValue(strategy = SEQUENCE,generator = "university_id_sequence_generator")
@EqualsAndHashCode.Exclude
private Long id;
@Column(name = "name")
private String name;
@Version
@Column(name = "audit_version")
@EqualsAndHashCode.Exclude
private Long auditVersion;
@OptimisticLock(excluded = true)
@OneToMany(mappedBy = "student")
@ToString.Exclude
private List<Student> student;
}
@Entity
@Table(name = "student")
@DynamicUpdate
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class Student {
@Id
@SequenceGenerator(name = "student_id_sequence_generator",sequenceName = "student_id_sequence",generator = "student_id_sequence_generator")
@EqualsAndHashCode.Exclude
private Long id;
@Column(name = "name")
private String name;
@Version
@Column(name = "audit_version")
@EqualsAndHashCode.Exclude
private Long auditVersion;
@OptimisticLock(excluded = true)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "university_id")
@ToString.Exclude
private University university;
}
似乎当我们分配大学然后保存Student时,如果我们在短时间内完成4个以上的任务,我们将获得OptimisticLockException。 似乎休眠状态是在University表上创建更新版本,即使University在数据库级别没有更改也是如此。
更新:保存学生的代码
Optional<University> universityInDB = universidyRepository.findById(universtityId);
universityInDB.ifPresent(university -> student.setUniversity(university);
Optional<Student> optionalExistingStudent = studentRepository.findById(student);
if (optionalExistingStudent.isPresent()) {
Student existingStudent = optionalExistingStudent.get();
if (!student.equals(existingStudent)) {
copyContentProperties(student,existingStudent);
studentToReturn = studentRepository.save(existingStudent);
} else {
studentToReturn = existingStudent;
}
} else {
studentToReturn = studentRepository.save(student);
}
private static final String[] IGNORE_PROPERTIES = {"id","createdOn","updatedOn","auditVersion"};
public void copyContentProperties(Object source,Object target) {
BeanUtils.copyProperties(source,target,Arrays.asList(IGNORE_PROPERTIES)));
}
我们尝试了以下
@OptimisticLock(excluded = true)
不起作用,仍然给出乐观锁异常。
@JoinColumn(name = "university_id",updatable=false)
仅保存更新,因为我们不保存更新
@JoinColumn(name = "university_id",insertable=false)
工作,但不保存关系,university_id始终为空
更改级联行为。
似乎唯一有意义的值是Cascade.DETACH
,但给出了一个org.springframework.dao.InvalidDataAccessApiUsageException:org.hibernate.TransientPropertyValueException:对象引用了一个未保存的临时实例-在刷新之前保存该临时实例。
虽然我们提供其他解决方案,但不确定该选择什么
- 给客户端一个409(冲突)错误
在409之后,客户必须重试其帖子。 对于通过队列发送的对象,队列将重试该条目 后来。 我们不希望客户管理此错误
- 在OptimisticLockException之后重试
这不是很干净,因为当条目来自队列时我们已经在做,但是到目前为止可能是最好的解决方案。
- 成为关系的父级所有者
如果没有大量关系,这可能很好,但是我们有可能甚至在1000中都在100中出现, 将 使对象变大以便在队列中或通过Rest调用发送。
- 悲观锁
我们的整个数据库当前处于乐观锁定状态 到目前为止,我们设法避免了这种情况。 不想仅仅因为这个而改变我们的整个锁定策略 案件。可能对模型的那个子集强制悲观锁定 但是我没有看能否做到。
解决方法
除非您需要它,否则不需要它。 这样做:
University universityProxy = universidyRepository.getOne(universityId);
student.setUniversity(universityProxy);
要分配University
,您无需将University
实体加载到上下文中。因为从技术上讲,您只需要使用以下方式保存学生记录:正确的外键(university_id
)。因此,当您拥有university_id
时,可以使用存储库方法getOne()
创建一个Hibernate代理。
说明
Hibernate在引擎盖下非常复杂。 **当您将实体加载到上下文中时,它会创建其字段的快照副本,并在您更改任何实体时保持跟踪**。它的功能更多...因此,我想这个解决方案是最简单的解决方案,并且应该会有所帮助(除非您在同一会话范围内的其他地方更改“ university”对象)。很难说何时隐藏其他部分。潜在问题
- 错误的
@OneToMany
映射
@OneToMany(mappedBy = "student") // should be (mappedBy = "university")
@ToString.Exclude
private List<Student> student;
- 集合应该被初始化。 Hibernate使用它自己的集合隐含符号,并且您不应该手动设置字段。仅调用
add()
或remove()
或clear()
之类的方法
private List<Student> student; // should be ... = new ArrayList<>();
* 总体上有些地方不清楚,例如studentRepository.findById(student);
。因此,如果您想获得正确的答案,则最好将问题弄清楚。
如果从Hibernate启用查询日志,那么值得查看您的ORM正在执行的查询。您可能会意识到您的ORM做得太多。
在您的应用程序属性或配置文件中启用hibernate.show_sql=true
如果您对Student
的单个更新成为对University
的更新,而对Students
的所有更新都对它进行了更新,我不会感到惊讶。一切都会变本加厉。
ORM和实体映射用于策略性地检索数据。不应将它们用于实际定义对象关系。
您将要访问策略并根据其在REST端点中的使用方式来设计您的实体。
您在问题中指定要保存Student
,但您注意到University
也会随着每次Student
更新而更新。
很可能永远不会有Student
应该更新University
保持实体精益!
您可以通过支持这种单向关系的方式来构造实体。我删除了一些注释只是为了演示结构。您将要记住,在创建实体时,您正在编写它们的检索方式...
public class University {
@Id
private Long id;
private String name;
private Long auditVersion;
@OneToMany
private List<Student> student;
}
public class Student {
@Id
private Long id;
private String name;
private Long auditVersion;
private Long universityId;
}
这将确保对学生的更新保持针对性和整洁。您只需为学生分配大学ID,即可建立这种关系。
您通常希望遵守LockExceptions。重试LockException只是将数据库欺负到提交中,随着应用程序扩展,将引起更多的麻烦。
您始终可以选择使用精益实体并创建将结果压缩在一起的自定义响应或消息对象。
ORM不能用于创建快捷方式
在索引/外键上使用SELECT
的性能结果与将它们加入的成本大致相同...您只引入了一点额外的网络延迟。第二次访问数据库并不总是一个坏主意。 (通常,这正是Hibernate提取实体的方式)
您不必编写查询,但是您仍然需要了解检索和更新策略。
您正在牺牲数据库性能,并为便捷的.getChild()方法引入了复杂性。您会发现通过删除注释而不是添加注释来解决更多的性能/锁定问题。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。