Hibernate 在保存之前执行另一个 select

Hibernate does another select before save

我有以下示例代码:

@Transactional
public void someMethod() {
    MyEntity myEntity = myRepo.findById(1).orElse(null);
    if (myEntity != null) {
        myEntity.setValue("something");
        myRepo.save(myEntity);
    }
}

并且myRepo是以下的实例:

public interface MyRepo extends CrudRepository<MyEntity, Integer> {}

所以我可以看到第一个 SELECT 来自 myRepo.findById(1),这是有道理的。但是当代码运行到 myRepo.save(myEntity) 时,Hibernate 不知何故认为从 findById() 返回的 myEntity 不是托管的,因此它再次执行另一个 SELECT 来创建一个托管实体,然后替换它的值使用 myEntity,然后执行更新。

能否解释一下为什么需要另一个 SELECT 以及如何避免?此外,方法上方的 @Transactional 注释不会改变此行为,无论有无它。

它来自 @Transactional 在 Spring 中不太明显的行为。 @Transactional 意味着两件事:

  1. 打开持久上下文(Hibernate 会话)
  2. 交易本身。

你有第二个 select 因为在你的情况下 @Transactional 根本不起作用。 Hibernate 为每次调用打开一个新的持久上下文

  1. myRepo.findById(1)
  2. myRepo.save(myEntity)

如果您想进行自调用,您需要将对服务的引用传递给调用事务方法的方法。

class SomeServiceImpl implements SomeService {

    public void doWork(SomeService self) {
        self.someMethod();
    }

    @Transactional
    public void someMethod() {
        MyEntity myEntity = myRepo.findById(1).orElse(null);
        if (myEntity != null) {
            myEntity.setValue("something");
            myRepo.save(myEntity);
        }
    }

}

例子

SomeService service;
service.doWork(service);

另一种方法是自动 self 到服务领域。但这不是很好,因为您必须通过归档注入使用自动软件。不可能使用构造函数进行自动化。所以很难对服务进行单元测试。