如何使用 Spring Data JPA 保存具有手动分配标识符的实体? [英] How to save entities with manually assigned identifiers using Spring Data JPA?

查看:18
本文介绍了如何使用 Spring Data JPA 保存具有手动分配标识符的实体?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm updating an existing code that handles the copy or raw data from one table into multiple objects within the same database.

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

Previously, every kind of object had a generated PK using a sequence for each table.

类似的东西:

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

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

In order to reuse existing IDs from the import table, we removed GeneratedValue for some entities, like that :

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

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

For this entity, I did not change my JpaRepository, looking like this :

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

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

Now I'm struggling to understand the following behaviour, within a spring transaction (@Transactional) with the default propagation and isolation level :

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

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

This is a big issue when my Entity (without generated value) is mapped to MyOtherEntity (with generated value) in a one or many relationship.

因此我有以下错误:

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,我不会收到任何错误.

Seems legit since the insert has not been sent for Entity, but why ? Again, if I change the ID of the Entity and use @GeneratedValue I don't get any error.

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

I'm using Spring Boot 1.5.12, Java 8 and PostgreSQL 9

推荐答案

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

You're basically switching from automatically assigned identifiers to manually defined ones which has a couple of consequences both on the JPA and Spring Data level.

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

On the plain JPA level, the persistence provider doesn't necessarily need to immediately execute a single insert as it doesn't have to obtain an identifier value. That's why it usually delays the execution of the statement until it needs to flush, which is on either an explicit call to EntityManager.flush(), a query execution as that requires the data in the database to be up to date to deliver correct results or transaction commit.

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

Spring Data JPA repositories automatically use default transactions on the call to save(…). However, if you're calling repositories within a method annotated with @Transactional in turn, the databse interaction might not occur until that method is left.

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

JPA requires the EntityManager client code to differentiate between persisting a completely new entity or applying changes to an existing one. Spring Data repositories w ant to free the client code from having to deal with this distinction as business code shouldn't be overloaded with that implementation detail. That means, Spring Data will somehow have to differentiate new entities from existing ones itself. The various strategies are described in the reference documentation.

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

In case of manually identifiers the default of inspecting the identifier property for null values will not work as the property will never be null by definition. A standard pattern is to tweak the entities to implement Persistable and keep a transient is-new-flag around and use entity callback annotations to flip the 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(…) 最初会触发 EntityManager.persist(…)….merge(…) 用于所有后续操作.

isNew is declared transient so that it doesn't get persisted. The type implements Persistable so that the Spring Data JPA implementation of the repository's save(…) method will use that. The code above results in entities created from user code using new having the flag set to true, but any kind of database interaction (saving or loading) turning the entity into a existing one, so that save(…) will trigger EntityManager.persist(…) initially but ….merge(…) for all subsequent operations.

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

I took the chance to create DATAJPA-1600 and added a summary of this description to the reference docs.

这篇关于如何使用 Spring Data JPA 保存具有手动分配标识符的实体?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆