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

Posted

技术标签:

【中文标题】如何使用 Spring Data JPA 保存具有手动分配标识符的实体?【英文标题】:How to save entities with manually assigned identifiers using Spring Data JPA? 【发布时间】:2019-12-31 20:05:18 【问题描述】:

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

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

类似的东西:

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

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

@Id
@Column(name = "id")
private Integer id;

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

public interface EntityRepository extends JpaRepository<Entity, Integer> 
    <S extends Entity> S save(S entity);

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

使用实体上的@GeneratedValue,当我调用 entityRepository.save(entity) 时,我可以看到 Hibernate show sql activate 触发了插入请求(但似乎只在缓存中,因为数据库没有改变) 如果实体上没有 @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"

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

我正在使用 Spring Boot 1.5.12、Java 8 和 PostgreSQL 9

【问题讨论】:

【参考方案1】:

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

数据库操作时序

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

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

EntityManager.persist(…) VS。 ….merge(…)

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

在手动标识符的情况下,检查标识符属性的 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…

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

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

【讨论】:

以上是关于如何使用 Spring Data JPA 保存具有手动分配标识符的实体?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Data JPA:保存嵌套的 OneToMany 子级(具有级联)返回 NULL 父级外部对象

具有多个数据源的春季批处理junit。 spring data jpa无法将数据保存在内存数据库中

如何使用Spring Data JPA在H2数据库中保存数据

保存后刷新并获取实体(JPA/Spring Data/Hibernate)

如何使用基于类的配置设置具有不同基础包、transactionManagerRef 的多个 Spring Data JPA?

Spring Data JPA save() 方法不遵循 hashcode/equals 合约