java—为什么hibernate在保存子对象时需要保存父对象,并且即使父对象没有更改也会导致optimisticlockexception?

frebpwbc  于 2021-07-12  发布在  Java
关注(0)|答案(2)|浏览(351)

我们正努力在短时间内拯救许多孩子,冬眠不断给孩子带来乐观。下面是一个简单的例子:

University
id
name
audit_version

Student 
id
name 
university_id
audit_version

其中大学id可以为空。
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", allocationSize = 1)
    @GeneratedValue(strategy = 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;
}

似乎当我们分配大学然后挽救学生时,如果我们在短时间内完成4个以上的任务,我们就会得到乐观的结果。hibernate似乎正在university表上创建更新版本,尽管university在db级别没有更改。
更新:保存学生的代码

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) 工作但不保存关系,并且大学id始终为空
改变级联行为。唯一有意义的价值是 Cascade.DETACH ,但给出一个org.springframework.dao.invaliddataaccessapiusageexception:org.hibernate.transientpropertyvalueexception:object引用一个未保存的临时示例-在刷新之前保存临时示例。
我们想到了另一个解决办法,但不确定该选什么
给客户端一个409(冲突)错误
在409之后,客户机必须重试他的post。对于通过队列发送的对象,队列稍后将重试该条目。我们不希望我们的客户管理这个错误
出现optimisticlockexception后重试
它并不干净,因为当条目来自队列时,我们已经在做了,但可能是迄今为止最好的解决方案。
使之成为关系的父所有者
如果没有大量的关系,这可能是好的,但是我们有可能在100甚至1000中的情况,这将使对象变大,以便在队列中或通过rest调用发送。
悲观锁
我们的整个数据库目前处于乐观锁定状态,到目前为止,我们设法防止了这种乐观锁定的情况,我们不想仅仅因为这种情况而改变我们的整个锁定策略。可能对模型的子集强制悲观锁定,但我还没有考虑是否可以这样做。

4sup72z8

4sup72z81#

它不需要它,除非你需要它。请执行以下操作:

University universityProxy = universidyRepository.getOne(universityId);
    student.setUniversity(universityProxy);

为了分配 University 你不必加载 University 将实体添加到上下文中。因为从技术上讲,你只需要用一个合适的外键保存一个学生记录( university_id ). 所以当你有一个 university_id ,您可以使用repository方法创建hibernate代理 getOne() .

解释

冬眠是相当复杂的引擎盖下**当您将实体加载到上下文中时,它将创建其字段的快照副本,并跟踪您是否更改了其中任何一个字段。它做的更多。。。因此,我猜这个解决方案是最简单的,应该会有所帮助(除非您在同一会话的范围内的其他地方更改“university”对象)。很难说其他部分何时隐藏。

潜在问题

@OneToMany Map

@OneToMany(mappedBy = "student") // should be (mappedBy = "university")
    @ToString.Exclude
    private List<Student> student;

应初始化集合。hibernate使用它自己的集合实现,您不应该手动设置字段。只调用如下方法 add() 或者 remove() ,或 clear() ```
private List student; // should be ... = new ArrayList<>();


* 总的来说有些地方不清楚,比如 `studentRepository.findById(student);` . 所以,如果你想得到一个正确的答案,最好是在你的问题清楚。
m1m5dgzv

m1m5dgzv2#

如果您从hibernate启用查询日志,那么可以查看orm正在执行的查询。你可能会意识到你的orm做得太多了。
在应用程序属性或配置文件中启用 hibernate.show_sql=true 如果你的一次更新 Student 成为对 University 它将成为其所有包含 Students . 每件事都会有一个版本。

orm和实体Map用于战略性地检索数据。它们不应用于实际定义对象关系。

您需要访问策略,并根据实体在其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,从而建立了这种关系。
您通常希望尊重锁例外。在lockexception上重试只是强迫您的数据库提交,并且随着应用程序的扩展会引起更多的麻烦。
您始终可以选择使用精益实体,并创建自定义响应或消息对象,将结果压缩到一起。

orms不用于创建快捷方式

一个系统的性能结果 SELECT 在索引/外键上,获取它们的成本大致相同。。。只会引入一点额外的网络延迟。第二次访问数据库并不总是一个坏主意(通常,hibernate就是这样获取实体的)
您不必编写查询,但仍需要了解检索和更新策略。
为了方便的.getchild()方法,您牺牲了数据库性能并引入了复杂性。您会发现,通过删除注解而不是添加注解,可以解决更多的性能/锁定问题。

相关问题