我有两个实体,Parent
和Child
,我希望它们共享一个主键,但是我希望这种关系是单向的。只有孩子应该了解父母。 spring-data-jpa存储库有可能吗?
我已经尝试过直接使用EntityManager,它可以正常工作。我能够先保留Parent
,然后再保留Child
实体。但是,当我使用Spring的JpaRepository
尝试相同操作时,出现以下错误:org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property
。
我尝试用自己的SERIAL ID和对父代的外键引用对Child
实体进行建模。可以,但是我更喜欢使用共享主键。
@Entity
public class Parent {
@Id
@Column(name = "code")
String code;
}
@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());
}
}
答案 0 :(得分:1)
以下代码可以正常工作:
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
。
得出的结论是,仅当实体为null(对于数字类型为0)时,它才是新实体。
因此,您的childRepo.save(child);
等效于entityManager.merge(child);
检查是否在第二个测试中调用合并,则收到的错误是否相同。
要解决此问题:
@Id
上设置值(可能您也想强迫lombok也不要为其生成设置器)。这将导致persist
被代替,而不是merge
code
和parent
映射到同一数据库列。为了使映射正确,我在insertable = false, updatable = false
列上使用了强制@Id
(更改parent
字段将导致生成正确的sql)