67

这是代码:

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

来自 Spring Data JPA 项目的JpaRepository 。

这是测试代码:

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\n", account.getId(), returnedAccount.getId());
    }
}

结果如下:

account ID is 0 and for returned account ID is 1

这是来自 CrudReporsitory.save() javadoc:

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

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

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

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

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

4

2 回答 2

70

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

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

因此,一般来说,对于实际实现而言,更多的 API 决定是宽松(允许的,宽容的),从而像我们一样实现 JPA 的方法。没有对传递的实体进行额外的代理按摩。

于 2011-12-26T09:45:07.237 回答
17

您错过了第二部分:如果实体不是新的,merge则调用。merge将其参数的状态复制到具有相同 ID 的附加实体中,并返回附加实体。如果实体不是新实体,并且您不使用返回的实体,您将对分离的实体进行修改。

于 2011-12-24T16:05:40.137 回答