JPA + Spring: сопоставление столбца внешнего ключа с идентификатором на основе значения, полученного перед сохранением объекта

Пожалуйста, извините меня, если это глупый вопрос, но я очень новичок в JPA и Spring. Мне интересно, может ли кто-нибудь помочь с проблемой ниже, так как я много искал в StackOverflow и Google, но не смог найти ни одного примера для моего варианта использования. Возможно, мне не хватает ключевого слова, так как я не знаю, как такое сопоставление называется в JPA.

Итак, в основном, я пытаюсь разработать REST API для извлечения и вставки записи из/в базу данных, для которой у меня есть две таблицы, а именно CLIENT_MASTER и TITLE.

Схема для CLIENT_MASTER выглядит следующим образом:

CREATE TABLE CLIENT_MASTER (
   ID INT GENERATED BY DEFAULT AS IDENTITY(START WITH 1000),
   TITLE_ID INT,
   FIRST_NAME VARCHAR(255),
   MIDDLE_NAME VARCHAR(255),
   LAST_NAME VARCHAR(255),
   PRIMARY KEY (ID)
);

Точно так же схема для таблицы TITLE такова:

CREATE TABLE TITLE (
   ID INT GENERATED BY DEFAULT AS IDENTITY,
   TITLE VARCHAR(10),
   PRIMARY KEY (ID)
);

Таблица CLIENT_MASTER имеет указанное ниже ограничение внешнего ключа:

ALTER TABLE CLIENT_MASTER ADD CONSTRAINT TITLE_ID FOREIGN KEY (TITLE_ID) REFERENCES TITLE;

Сущность для обеих таблиц определяется, как показано ниже:

import java.io.Serializable;
...
import lombok.Setter;

@Entity
@Table(name = "CLIENT_MASTER")
@Getter
@Setter
public class ClientMaster implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", updatable = false, nullable = false)
    private Integer id;

    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "TITLE_ID")
    private Title title;

    @Column(name = "FIRST_NAME")
    private String firstName;

    @Column(name = "MIDDLE_NAME")
    private String middleName;

    @Column(name = "LAST_NAME")
    private String lastName;
}

import java.io.Serializable;
....
import lombok.Setter;

@Entity
@Table(name = "TITLE")
@Getter
@Setter
public class Title implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", updatable = false, nullable = false)
    private Integer id;

    @Column(name = "TITLE")
    private String title;
}

И в моем контроллере отдыха у меня есть ниже, чтобы вставить новый клиент

@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<?> create(@RequestBody ClientMaster cm) {
    cmrepository.create(cm);
    HttpHeaders headers = new HttpHeaders();
    ControllerLinkBuilder linkBuilder = linkTo(methodOn(CompanyController.class).get(cm.getId()));
    headers.setLocation(linkBuilder.toUri());
    return new ResponseEntity<>(headers, HttpStatus.CREATED);
}

где cmrepository - экземпляр,

@Repository
public class CMRepository {

@PersistenceContext
private EntityManager entityManager;

@Override
public void create(ClientMaster cm) {
    entityManager.persist(cm);

 }
}

Я отправляю приведенный ниже json в почтовом запросе, и я хочу сопоставить значение заголовка с соответствующим идентификатором из таблицы TITLE и вставить запись с правильным идентификатором заголовка.

{
    "title": {
        "title": "Mr"
    },
    "firstName": "XXX",
    "middleName": "YYY",
    "lastName": "ZZZ",
}

С учетом вышеизложенного текущее поведение заключается в том, что для каждой вставки записи в CLIENT_TABLE создается новая запись в таблице TITLE, чего я не хочу.

Может ли кто-нибудь указать мне в правильном направлении, как этого можно достичь?

Огромное спасибо.

Вместо CascadeType.ALL Попробуйте установить только MERGE

vc73 09.04.2019 14:49

@ vc73 Замена CascadeType на MERGE, к сожалению, не сработала.

Nishant 09.04.2019 19:02
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
5 279
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Невозможно, чтобы поставщик постоянства автоматически принимал идентификатор по значению поля.

Вы должны:

  1. Получите запись Title сначала по title.
  2. Установите title на ClientMaster, если он был получен. В противном случае ничего не делайте и позвольте каскаду сохранить это значение.
  3. Используйте merge вместо persist для объекта ClientMaster:

    cmrepository.merge(cm);

Почему merge a persist будет нормально работать даже с существующими объектами ссылок.

M. Deinum 09.04.2019 15:09

Если вы извлечете объект Title.. он будет в отсоединенном состоянии, прежде чем сохранится. И если вы попытаетесь сохранить отдельный объект (через каскад здесь), вы получите исключение

Maciej Kowalski 09.04.2019 15:39

Если Transactional не установлен на уровне контроллера. но этого мы не знаем

Maciej Kowalski 09.04.2019 15:40

Большое спасибо, ребята, я разместил свой ответ ниже. Извините, я не могу пометить этот ответ как полезный, потому что у меня еще недостаточно репутации.

Nishant 09.04.2019 18:55

Я не думаю, что это хорошая идея, чтобы он не вставлялся, хотя, поскольку вы определили ключевое отношение forgien, всегда лучше, чтобы в таблице заголовков были сохранены все идентификаторы.

Нет, есть только предопределенный набор заголовков, которые инициализируются после создания таблицы. Если заголовок в запросе не совпадает ни с одним из существующих заголовков, то конечный пользователь не сможет вставить какую-либо запись в таблицу client_master. Это в основном требование.

Nishant 09.04.2019 18:59

Спасибо вам, ребята

Я последовал совету Мацея, и теперь мой контроллер выглядит так, как показано ниже.

@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<?> create(@RequestBody ClientMaster company) {
    //fetch and set title. 
    company.setTitle(titleService.get(company.getTitle().getTitle()));
    companyService.create(company);
    HttpHeaders headers = new HttpHeaders();
    ControllerLinkBuilder linkBuilder = linkTo(methodOn(CompanyController.class).get(company.getId()));
    headers.setLocation(linkBuilder.toUri());
    return new ResponseEntity<>(headers, HttpStatus.CREATED);
}

Другие вопросы по теме