为什么在 Spring Data JPA Repository 上的 save() 之后使用返回的实例?

2022-01-18 00:00:00 spring java jpa spring-data spring-data-jpa

代码如下:

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {}

JpaRepository 来自 Spring Data JPA 项目.

JpaRepository from Spring Data JPA project.

这里是测试代码:

public class JpaAccountRepositoryTest extends JpaRepositoryTest {
    @Inject
    private AccountRepository accountRepository;

    @Inject
    private Account account;

    @Test
    @Transactional
    public void createAccount() {
        Account returnedAccount = accountRepository.save(account);

        System.out.printf("account ID is %d and for returned account ID is %d
", account.getId(), returnedAccount.getId());
    }
}

结果如下:

account ID is 0 and for returned account ID is 1

这里来自 CrudReporsitory.save() javadoc:

Here is from CrudReporsitory.save() javadoc:

保存给定的实体.使用返回的实例进行进一步的操作,因为保存操作可能已经完全改变了实体实例.

Saves a given entity. Use the returned instance for further operations as the save operation might have changed the entity instance completely.

这里是 Spring Data JPA 中 SimpleJpaRepository 的实际代码:

Here is the actual code for SimpleJpaRepository from Spring Data JPA:

 @Transactional
    public T save(T entity) { 
            if (entityInformation.isNew(entity)) {
                    em.persist(entity);
                    return entity;
            } else {
                    return em.merge(entity);
            }
    }

那么,问题是为什么我们需要使用返回的实例而不是原始实例?(是的,我们必须这样做,否则我们会继续使用分离的实例,但是为什么)

So, the question is why do we need to use the returned instance instead of the original one? (yes, we must do it, otherwise we continue to work with detached instance, but why)

原来的 EntityManager.persist() 方法返回 void,所以我们的实例附加到持久化上下文.传递帐户以保存到存储库时是否会发生一些代理魔术?是不是 Spring Data JPA 项目的架构限制?

The original EntityManager.persist() method returns void, so our instance is attached to the persistence context. Does some proxy magic happens while passing account to save to repository? Is it the architecture limitation of Spring Data JPA project?

推荐答案

CrudRepository 接口的save(…) 方法应该是抽象的简单地存储一个实体 no不管它处于什么状态.因此它不能暴露实际的存储特定实现,即使(如在 JPA 中)存储区分要存储的新实体和要更新的现有实体.这就是为什么该方法实际上被称为 save(...) 而不是 create(...)update(...).我们从该方法返回一个结果,实际上允许 store 实现返回一个完全不同的实例,就像 JPA 在调用 merge(...) 时可能做的那样.

The save(…) method of the CrudRepository interface is supposed to abstract simply storing an entity no matter what state it is in. Thus it must not expose the actual store specific implementation, even if (as in the JPA) case the store differentiates between new entities to be stored and existing ones to be updated. That's why the method is actually called save(…) not create(…) or update(…). We return a result from that method to actually allow the store implementation to return a completely different instance as JPA potentially does when merge(…) gets invoked.

此外,如果实际实现需要填充标识符等,实际上能够处理不可变对象(即不是 JPA)的持久性实现可能必须返回一个新实例.IE.假设实现只会消耗实体状态通常是错误的.

Also, persistence implementations actually capable of dealing with immutable objects (i.e. not JPA) might have to return a fresh instance if the actual implementation requires populating an identifier or the like. I.e. it's generally wrong to assume that the implementation would just consume the entity state.

所以一般来说,API 决定对实际实现保持宽松(允许的、宽容的),从而像我们一样为 JPA 实现方法.没有对传递的实体进行额外的代理按摩.

So generally it's more of an API decision to be lenient (permissible, tolerant) regarding the actual implementation and thus implementing the method for JPA as we do. There's no additional proxy massaging done to the entities passed.

相关文章