为什么休眠状态在保存子级时需要保存父级,并且即使父级没有变化也导致OptimisticLockException? 说明潜在问题 ORM和实体映射用于策略性地检索数据不应将它们用于实际定义对象关系

如何解决为什么休眠状态在保存子级时需要保存父级,并且即使父级没有变化也导致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:对象引用了一个未保存的临时实例-在刷新之前保存该临时实例。

虽然我们提供其他解决方案,但不确定该选择什么

  1. 给客户端一个409(冲突)错误

在409之后,客户必须重试其帖子。 对于通过队列发送的对象,队列将重试该条目 后来。 我们不希望客户管理此错误

  1. 在OptimisticLockException之后重试

这不是很干净,因为当条目来自队列时我们已经在做,但是到目前为止可能是最好的解决方案。

  1. 成为关系的父级所有者

如果没有大量关系,这可能很好,但是我们有可能甚至在1000中都在100中出现, 将 使对象变大以便在队列中或通过Rest调用发送。

  1. 悲观锁

我们的整个数据库当前处于乐观锁定状态 到目前为止,我们设法避免了这种情况。 不想仅仅因为这个而改变我们的整个锁定策略 案件。可能对模型的那个子集强制悲观锁定 但是我没有看能否做到。

解决方法

除非您需要它,否则不需要它。 这样做:

    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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-