如何使用 Spring Data JPA 保存具有手动分配标识符的实体?

Aur*_*lin 6 java hibernate spring-data spring-data-jpa spring-boot

我正在更新现有代码,该代码将一个表中的副本或原始数据处理为同一数据库中的多个对象。

以前,每种对象都有一个使用每个表的序列生成的 PK。

类似的东西:

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
Run Code Online (Sandbox Code Playgroud)

为了重用导入表中的现有 ID,我们删除了某些实体的 GeneratedValue,如下所示:

@Id
@Column(name = "id")
private Integer id;
Run Code Online (Sandbox Code Playgroud)

对于这个实体,我没有改变我的 JpaRepository,看起来像这样:

public interface EntityRepository extends JpaRepository<Entity, Integer> {
    <S extends Entity> S save(S entity);
}
Run Code Online (Sandbox Code Playgroud)

现在我正在努力理解以下行为,在具有默认传播和隔离级别的 spring 事务 (@Transactional) 中:

  • 使用实体上的@GeneratedValue,当我调用 entityRepository.save(entity) 时,我可以看到 Hibernate show sql 已激活,插入请求被触发(但似乎只在缓存中,因为数据库没有改变)
  • 在实体上没有 @GeneratedValue 时,只会触发一个选择请求(没有插入尝试)

当我的实体(没有生成的值)在一个或多个关系中映射到 MyOtherEntity(有生成的值)时,这是一个大问题。

因此,我有以下错误:

ERROR: insert or update on table "t_other_entity" violates foreign key constraint "other_entity_entity"
Détail : Key (entity_id)=(110) is not present in table "t_entity"
Run Code Online (Sandbox Code Playgroud)

似乎合法,因为尚未为实体发送插入内容,但为什么呢?同样,如果我更改实体的 ID 并使用 @GeneratedValue 我不会收到任何错误。

我使用的是 Spring Boot 1.5.12、Java 8 和 PostgreSQL 9

Oli*_*ohm 9

您基本上是从自动分配的标识符切换到手动定义的标识符,这对 JPA 和 Spring Data 级别都有一些影响。

数据库操作时序

在普通 JPA 级别,持久性提供程序不一定需要立即执行单个插入,因为它不必获取标识符值。这就是为什么它通常会延迟语句的执行直到它需要刷新,这要么是显式调用EntityManager.flush(),要么是查询执行,因为它需要数据库中的数据是最新的,以提供正确的结果或事务提交。

Spring Data JPA 存储库在调用save(…). 但是,如果您@Transactional在依次注释的方法中调用存储库,则在离开该方法之前可能不会发生数据库交互。

EntityManager.persist(…) 对比。 ….merge(…)

JPA 要求EntityManager客户端代码区分持久化一个全新的实体或对现有实体应用更改。Spring Data 存储库希望将客户端代码从必须处理这种区别中解放出来,因为业务代码不应该因实现细节而过载。这意味着,Spring Data 必须以某种方式将新实体与现有实体区分开来。参考文档中描述了各种策略。

在手动标识符的情况下,检查标识符属性的默认null值将不起作用,因为该属性永远不会被null定义。标准模式是调整实体以实现Persistable并保持一个瞬态 is-new-flag 并使用实体回调注释来翻转标志。

@MappedSuperclass
public abstract class AbstractEntity<ID extends SalespointIdentifier> implements Persistable<ID> {

  private @Transient boolean isNew = true;

  @Override
  public boolean isNew() {
    return isNew;
  }


  @PrePersist
  @PostLoad
  void markNotNew() {
    this.isNew = false;
  }

  // More code…
}
Run Code Online (Sandbox Code Playgroud)

isNew被声明为瞬态,因此它不会被持久化。该类型实现,Persistable以便存储库save(…)方法的 Spring Data JPA 实现将使用它。上面的代码导致使用new标志设置为从用户代码创建的实体true,但任何类型的数据库交互(保存或加载)将实体转换为现有实体,因此这save(…)EntityManager.persist(…)最初触发,但….merge(…)对于所有后续操作。

我借此机会创建了DATAJPA-1600并将此描述的摘要添加到参考文档中。