为什么在Spring Data JPA存储库上使用save()后返回实例?

71
这里是代码:
@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {}

JpaRepository来自Spring Data JPA项目。

以下是测试代码:

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()的Java文档:

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

以下是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项目的架构限制吗?
2个回答

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

此外,实际上能够处理不可变对象(即非JPA)的持久化实现可能需要返回一个新实例,如果实际实现需要填充标识符或类似内容。也就是说,通常错误的做法是假设实现只消耗实体状态。

因此,通常更多地是API决策对实际实现放宽(允许,容忍)要求,因此像我们为JPA所做的那样来实现该方法。传递的实体不需要额外的代理修饰。


我可以假设不使用返回结果来创建新实体是安全的吗? - johnlemon
4
有关返回对象状态的文档在哪里?即有时它会返回更新后的字段(时间戳),有时这些字段不会从数据库中更新。 - Don Cheadle
1
我这里有一个问题,我正在使用 save 保存一个实体,其中有几个空列。但是这些列在数据库中具有默认值,并且在数据库中保存正常。但是,从 save 返回的实体对于默认列具有所有空值。为什么会出现这种行为? - Naanavanalla
如果JPA提供程序不知道数据库中这些值具有默认值,它将无法实现这些默认值。JPA知道@GeneratedValue,但指定仅与主键一起使用。也就是说,JPA目前不支持此功能。 - Oliver Drotbohm
2
乐观锁和@Version列也存在类似的问题。它在数据库中被更新了,但是在返回的实体中没有被更新。JPA应该明确知道它已经被更新了... - mirec

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

是的,同意。至少在这种情况下它与EntityManager兼容。将这个方法重命名为saveOrMerge()会更好吗? - Aliaksandr Kazlou
对象的瞬态成员怎么办?当合并时,我得到一个全新的实际对象(说实话我不在意),但我失去了所有原始对象的瞬态值。 - David Hergert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接