如何使用共享主键保存 child 个实体
How to save child entity with shared primary key
问题
我有两个实体,Parent
和 Child
,我希望它们共享一个主键,但我希望关系是 uni-directional。只有 child 应该知道 parent。 spring-data-jpa 个存储库可以做到这一点吗?
我试过的
我试过直接使用 EntityManager 并且它有效。我能够先保留 Parent
,然后保留 Child
实体。但是,当我尝试使用 Spring 的 JpaRepository
进行相同操作时,出现以下错误:org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property
.
我已经尝试使用自己的序列号和对 parent 的外键引用以不同方式对 Child
实体进行建模。这可行,但我更愿意使用共享主键。
Parent实体:
@Entity
public class Parent {
@Id
@Column(name = "code")
String code;
}
Child实体:
@Entity
public class Child {
@Id
@Column(name = "code")
String code;
@MapsId
@JoinColumn(name = "code")
@OneToOne
Parent parent;
}
测试:
@RunWith(SpringRunner.class)
@DataJpaTest
@Slf4j
public class MapsByIdTest {
@Autowired
ChildRepository childRepo;
@Autowired
ParentRepo parentRepo;
@Autowired
EntityManager entityManager;
@Test // FAILS with org.springframework.orm.jpa.JpaSystemException
public void testSpringDataJpaRepository() {
Parent pA = parentRepo.save(Parent.builder().code("A").build());
Child child = Child.builder().parent(pA).code("A").build();
childRepo.save(child);
}
@Test // WORKS!
public void testEntityManager() {
Parent p = Parent.builder().code("A").build();
entityManager.persist(p);
Child child = Child.builder().code("A").parent(p).build();
entityManager.persist(child);
log.info(entityManager.find(Parent.class, "A").toString());
log.info(entityManager.find(Child.class, "A").toString());
}
}
以下代码可以正常工作:
public class Child {
@Id
@Column(name = "code", insertable = false, updatable = false)
String code;
@MapsId
@JoinColumn(name = "code")
@OneToOne
Parent parent;
}
并测试
@Test
@Transactional
public void testSpringDataJpaRepository() {
Parent pA = parentRepo.save(Parent.builder().code("A").build());
Child child = Child.builder().parent(pA).build();
childRepo.save(child);
}
解释一下:
看SimpleJpaRepository.save
的实现
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
比检查AbstractEntityInformation.isNew
。
它得出结论,只有当实体为空(或数字类型为 0)时,该实体才是新的。
因此,您的 childRepo.save(child);
等同于 entityManager.merge(child);
检查如果您在第二次测试中调用合并,您收到的错误是相同的。
解决问题:
- 不要在你的 child
@Id
上设置值(可能你想强制 lombok 也不要为它生成 setter)。这将导致 persist
而不是 merge
- 请注意,您有 2 个字段
code
和 parent
映射到同一个数据库列。为了使映射正确,我在 @Id
列上使用了强制 insertable = false, updatable = false
(parent
字段的更改将导致生成正确的 sql)
问题
我有两个实体,Parent
和 Child
,我希望它们共享一个主键,但我希望关系是 uni-directional。只有 child 应该知道 parent。 spring-data-jpa 个存储库可以做到这一点吗?
我试过的
我试过直接使用 EntityManager 并且它有效。我能够先保留
Parent
,然后保留Child
实体。但是,当我尝试使用 Spring 的JpaRepository
进行相同操作时,出现以下错误:org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property
.我已经尝试使用自己的序列号和对 parent 的外键引用以不同方式对
Child
实体进行建模。这可行,但我更愿意使用共享主键。
Parent实体:
@Entity
public class Parent {
@Id
@Column(name = "code")
String code;
}
Child实体:
@Entity
public class Child {
@Id
@Column(name = "code")
String code;
@MapsId
@JoinColumn(name = "code")
@OneToOne
Parent parent;
}
测试:
@RunWith(SpringRunner.class)
@DataJpaTest
@Slf4j
public class MapsByIdTest {
@Autowired
ChildRepository childRepo;
@Autowired
ParentRepo parentRepo;
@Autowired
EntityManager entityManager;
@Test // FAILS with org.springframework.orm.jpa.JpaSystemException
public void testSpringDataJpaRepository() {
Parent pA = parentRepo.save(Parent.builder().code("A").build());
Child child = Child.builder().parent(pA).code("A").build();
childRepo.save(child);
}
@Test // WORKS!
public void testEntityManager() {
Parent p = Parent.builder().code("A").build();
entityManager.persist(p);
Child child = Child.builder().code("A").parent(p).build();
entityManager.persist(child);
log.info(entityManager.find(Parent.class, "A").toString());
log.info(entityManager.find(Child.class, "A").toString());
}
}
以下代码可以正常工作:
public class Child {
@Id
@Column(name = "code", insertable = false, updatable = false)
String code;
@MapsId
@JoinColumn(name = "code")
@OneToOne
Parent parent;
}
并测试
@Test
@Transactional
public void testSpringDataJpaRepository() {
Parent pA = parentRepo.save(Parent.builder().code("A").build());
Child child = Child.builder().parent(pA).build();
childRepo.save(child);
}
解释一下:
看SimpleJpaRepository.save
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
比检查AbstractEntityInformation.isNew
。
它得出结论,只有当实体为空(或数字类型为 0)时,该实体才是新的。
因此,您的 childRepo.save(child);
等同于 entityManager.merge(child);
检查如果您在第二次测试中调用合并,您收到的错误是相同的。
解决问题:
- 不要在你的 child
@Id
上设置值(可能你想强制 lombok 也不要为它生成 setter)。这将导致persist
而不是merge
- 请注意,您有 2 个字段
code
和parent
映射到同一个数据库列。为了使映射正确,我在@Id
列上使用了强制insertable = false, updatable = false
(parent
字段的更改将导致生成正确的 sql)