如何使用共享主键保存 child 个实体

How to save child entity with shared primary key

问题

我有两个实体,ParentChild,我希望它们共享一个主键,但我希望关系是 uni-directional。只有 child 应该知道 parent。 spring-data-jpa 个存储库可以做到这一点吗?

我试过的

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 个字段 codeparent 映射到同一个数据库列。为了使映射正确,我在 @Id 列上使用了强制 insertable = false, updatable = falseparent 字段的更改将导致生成正确的 sql)