以下是代码:
@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:
保存给定的实体。使用返回的实例进行进一步操作,因为save操作可能已完全更改了实体实例。
以下是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项目的架构限制吗?
答案 0 :(得分:59)
save(…)
接口的CrudRepository
方法应该抽象简单地存储一个实体,无论它处于什么状态。因此它不能暴露实际的商店特定实现,即使(如在(JPA)案例,商店区分要存储的新实体和要更新的现有实体。这就是为什么该方法实际上被称为save(…)
而不是create(…)
或update(…)
。我们从该方法返回一个结果,实际上允许商店实现返回一个完全不同的实例,因为JPA在调用merge(…)
时可能会这样做。
因此,一般来说,更多的API决定对于实际实现是宽松的(允许的,容忍的),从而像我们一样实现JPA的方法。传递的实体没有额外的代理消息传递。
答案 1 :(得分:13)
您错过了第二部分:如果实体不是新实体,则调用merge
。 merge
将其参数的状态复制到具有相同ID的附加实体中,并返回附加的实体。如果实体不是新实体,并且您不使用返回的实体,则您将对分离的实体进行修改。