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

Posted

技术标签:

【中文标题】为啥在 Spring Data JPA Repository 上的 save() 之后使用返回的实例?【英文标题】:Why use returned instance after save() on Spring Data JPA Repository?为什么在 Spring Data JPA Repository 上的 save() 之后使用返回的实例? 【发布时间】:2012-01-27 08:24:33 【问题描述】:

代码如下:

@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() 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 项目的架构限制?

【问题讨论】:

【参考方案1】:

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

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

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

【讨论】:

我可以假设不将返回结果用于新实体是安全的吗? 返回对象状态的文档在哪里?即,有时它会返回更新的字段(时间戳),有时这些字段不会从数据库中更新。 我有一个问题,我正在使用save 保存一个实体,该实体几乎没有空列。但是这些列在 DB 中有默认值,并且在 DB 中保存得很好。但是,save 返回的实体的默认列具有所有空值。为什么会有这种行为? 如果 JPA 提供者不知道这些值在数据库中具有默认值这一事实,它将无法实现这些值。 JPA 知道@GeneratedValue,但它被指定为仅与主键一起使用。 IE。 JPA 目前根本不支持这个。 乐观锁定和@Version 列的类似问题。它在数据库中更新,但在返回的实体中没有更新。 JPA 肯定知道它已更新...【参考方案2】:

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

【讨论】:

是的,同意。至少,在这种情况下,它与 EntityManager 兼容。将此方法重命名为 saveOrMerge() 会更好吗? 对象的临时成员呢?在它合并的情况下,我得到一个全新的实际对象(老实说我并不关心),但我失去了所有原始对象的瞬态值。

以上是关于为啥在 Spring Data JPA Repository 上的 save() 之后使用返回的实例?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在将 Spring Data JPA 与 Spring Boot 结合使用时,我的数据库自定义没有得到应用?

Spring data jpa repo,为啥需要接口服务和服务实现

为啥手动定义的 Spring Data JPA 删除查询不会触发级联?

为啥使用 Spring Data JPA 更新实体时@Transactional 隔离级别不起作用?

Spring Data JPA 审计不适用于带有 @Modifying 注释的 JpaRepository 更新方法,为啥?

为啥在单个记录上调用 Spring Data JPA save() 而不是在记录列表上调用它时速度很慢?