Как сохранить дочернюю сущность с общим первичным ключом - PullRequest
0 голосов
/ 26 апреля 2019

Проблема

У меня есть две сущности, Parent & Child, я хочу, чтобы они разделили первичный ключ, но я хочу, чтобы отношения были однонаправленными. Только ребенок должен знать о родителе. Возможно ли это с репозиториями spring-data-jpa?

Что я пробовал

  • Я пытался использовать EntityManager напрямую, и он работает. Я могу сначала сохранить Parent, а затем сохранить Child сущность. Однако, когда я пытаюсь сделать то же самое с помощью Spring 101 *, я получаю следующую ошибку: org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property.

  • Я пытался смоделировать сущность Child по-разному, используя собственный идентификатор SERIAL и ссылку внешнего ключа на родителя. Это работает, но я бы предпочел использовать общий первичный ключ.

Родительское лицо:

@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());

  }
}

1 Ответ

1 голос
/ 27 апреля 2019

Следующий код будет работать нормально:

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); Убедитесь, что при вызове слияния во втором тесте ошибка, которую вы получаете, идентична.

Для решения проблемы:

  • не устанавливайте значение для вашего ребенка @Id (возможно, вы хотите, чтобы lombok также не генерировал для него сеттер). Это приведет к разрешению persist вместо merge
  • обратите внимание, что у вас есть 2 поля code и parent, сопоставленные одному столбцу БД. Чтобы сделать сопоставление правильным, я использовал принудительный insertable = false, updatable = false для столбца @Id (изменение поля parent приведет к созданию правильного sql)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...