我正在更新一个现有代码,该代码将从一个表的副本或原始数据处理为同一数据库中的多个对象。
以前,每种对象都有一个生成的 PK,使用每个表的序列。
像这样:
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
为了重用导入表中的现有 ID,我们删除了某些实体的 Generated Value,如下所示:
@Id
@Column(name = "id")
private Integer id;
对于这个实体,我没有更改我的 JpaRepository,如下所示:
public interface EntityRepository extends JpaRepository<Entity, Integer> {
<S extends Entity> S save(S entity);
}
现在我正在努力理解以下行为,在具有默认传播和隔离级别的 Spring 事务 (@Transactional( 中:
- 使用实体上的@GeneratedValue,当我调用 entityRepository.save(entity( 时,我可以看到 Hibernate 显示 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"
似乎合法,因为插入尚未为实体发送,但为什么?同样,如果我更改实体的 ID 并使用@GeneratedValue则不会收到任何错误。
我正在使用Spring Boot 1.5.12,Java 8和PostgreSQL 9
你基本上是从自动分配的标识符切换到手动定义的标识符,这在 JPA 和 Spring 数据级别都有几个后果。
数据库操作计时
在普通 JPA 级别,持久性提供程序不一定需要立即执行单个插入,因为它不必获取标识符值。这就是为什么它通常会延迟语句的执行,直到它需要刷新,这是对EntityManager.flush()
的显式调用,查询执行,因为这需要数据库中的数据是最新的,以提供正确的结果或事务提交。
Spring 数据 JPA 存储库在调用save(…)
时自动使用默认事务。但是,如果要在依次用@Transactional
注释的方法中调用存储库,则在离开该方法之前,可能不会发生 databse 交互。
EntityManager.persist(…)
VS.….merge(…)
JPA 要求EntityManager
客户端代码区分是持久化全新的实体还是将更改应用于现有实体。Spring 数据存储库将客户端代码从处理这种区别中解放出来,因为业务代码不应该因该实现细节而过载。这意味着,Spring Data将不得不以某种方式将新实体与现有实体区分开来。参考文档中介绍了各种策略。
在手动标识符的情况下,检查标识符属性以获取null
值的默认值将不起作用,因为根据定义,该属性永远不会null
。标准模式是调整实体以实现Persistable
,并保留瞬态 is-new-标志,并使用实体回调注释来翻转标志。
@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 实现将使用该实现。上面的代码导致使用将标志设置为true
new
从用户代码创建的实体,但任何类型的数据库交互(保存或加载(将实体转换为现有实体,以便save(…)
最初将触发EntityManager.persist(…)
,但对所有后续操作….merge(…)
。
我借此机会创建了 DATAJPA-1600,并将此描述的摘要添加到参考文档中。