【问题标题】:How to avoid ovewriting data in entity如何避免覆盖实体中的数据
【发布时间】:2021-08-08 12:14:19
【问题描述】:

我确实对 jpa 的工作方式有疑问,以及如何避免困扰我的情况。

好的,让我从简单的 sn-p 开始。

    @GetMapping("/")
    public List<Employee> index() {
        Employee employee1 = employeeRepository.findById(1L).get();
        Employee employee2 = ovewrite(employee1);
        Employee employee3 = employeeRepository.findById(1L).get();

        return Arrays.asList(employee1, employee2, employee3);
    }

    private Employee ovewrite(Employee employee){
        employee.setFirstName("Test");
        return employee;
    }

如您所见,我创建了一些代码来测试使用id == 1 找到的员工会发生什么。 我确实从 db 获得了 employee1,将其传递给了 ovewrite 方法,该方法只是更改了 firstName,之后我确实从 db 获得了同一个员工。

我在这里不明白的是,即使没有对数据库进行此更改的提交,employee3 也会更改名称。我想 jpa 正在缓存这些数据,并且每个新的 find 到这个对象我们都会得到修改记录?

还有一个问题,我怎样才能避免这种情况?因为我确实有一个带有这种“错误”的项目。对我来说,解决这个问题的最简单方法就是禁止这个同步化的孩子。

[编辑] 所以我试图通过在新事务中调用find 来避免这个缓存“问题”。但是,我的 employee3 仍然更改了 name 属性。

@GetMapping("/")
    public List<Employee> index() {
        Employee employee1 = employeeRepository.findById(1L).get();
        ovewrite(employee1);
        Employee employee3 = newTxFind();
        return Arrays.asList(employee1, employee3);
    }


    private Employee ovewrite(Employee employee){
        employee.setFirstName("Test");
        return employee;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Employee newTxFind(){
        return employeeRepository.findById(1L).get();
    }

【问题讨论】:

  • 请阅读:Can I ask only one question per post? --- 方法 index 是否有可能被 @Transactional 注释?
  • @Turing85 不,你在我的代码中看到的 sn-p 是在粘贴在这里时运行的
  • 那么它可能 - 实际上 - 是由于缓存。你能检查employee2 == employee3的值吗?
  • AFAIK Spring Boot 使用一个名为“Open Session In View”(OSIV)的概念。这将为控制器请求打开一个 Hibernate 会话,并在该请求完成后关闭它。因此,Hibernate 缓存很可能适用于两个调用employeeRepository.findById(1L)。看看,如果禁用 OSIV (spring.jpa.open-in-view = false) 会改变行为。 (我不太确定......)
  • @Seelenvirtuose 是对的,只是查了一下。 open-in-view 默认激活(详见this article over at Baeldung)。这意味着从数据库中获取的实体仍然附加到事务上下文中。作为回报,这意味着对实体的更改会自动提交到数据库。为了防止这种行为,我们有两个通用选项:在另一个事务中执行获取或通过实体管理器显式分离实体。

标签: java spring spring-boot jpa


【解决方案1】:

确实emplyee1 和employee3 将是同一个对象,这与JPA 缓存有关。所有 JPA 实现都至少具有 1 级缓存,即会话/事务缓存。您可以通过以下方式确认:

System.out.println(System.identityHashCode(employee1));
System.out.println(System.identityHashCode(employee3));

请注意,findById(1L) 返回的对象仅对于当前 JPA 事务是“相同的”。如果同时从另一个线程并行执行相同的 findById(1L),则 findById(1L) 的输出将是不同的对象。如果出于某种原因您在由

注释的方法中调用 findById(1L)
@javax.transaction.Transactional(REQUIRES_NEW)

或通过

@org.springframework.transaction.annotation.Transactional(propagation = Propagation = REQUIRES_NEW)

如果您想在修改employee1 对象时避免更改数据库,您可以将其从会话中分离,如果您确实需要这样做的话。分离的对象可以稍后重新连接,但有一些注意事项,很可能有更好的解决方法。

【讨论】:

  • 我尝试在新的 tx 中调用 find。但似乎我仍然得到相同的结果。我在第一篇文章中粘贴了代码。
【解决方案2】:

我认为不要多次调用 jpa 查询。最好为 Employee 对象创建一个空实例并将整个原始数据复制到其中,然后再调用覆盖函数。在所有过程之后,您应该拥有原始数据并覆盖数据.

@GetMapping("/")
    public List < Employee > index() {
        Employee employee3 = new Employee();
        Employee employee1 = employeeRepository.findById(1 L).get();
        //apache common library you may find it other alternatives too
        BeanUtils.copyProperties(employee3, employee1):
        ovewrite(employee1);
        return Arrays.asList(employee1, employee3);
    }

    private Employee ovewrite(Employee employee) {
        employee.setFirstName("Test");
        return employee;
    }

【讨论】:

    【解决方案3】:

    当你写作时

    Thing thing2 = thing1;
    

    它不会创造新事物。你只有一件事。因此,您的变量employee1 和employee2 指的是同一个Employee。

    【讨论】:

    • OP 谈论的是employee3,而不是employee2
    • 我不应该介绍employee2,我的错
    • 啊,我明白了。我想这是对引用语义的常见误解。
    猜你喜欢
    • 2015-03-20
    • 2022-01-22
    • 1970-01-01
    • 2021-11-10
    • 2015-05-15
    • 1970-01-01
    • 2021-04-18
    • 1970-01-01
    • 2020-11-21
    相关资源
    最近更新 更多