Пожалуйста, извините меня, если это глупый вопрос, но я очень новичок в 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
, чего я не хочу.
Может ли кто-нибудь указать мне в правильном направлении, как этого можно достичь?
Огромное спасибо.
@ vc73 Замена CascadeType
на MERGE
, к сожалению, не сработала.
Невозможно, чтобы поставщик постоянства автоматически принимал идентификатор по значению поля.
Вы должны:
Title
сначала по title
.title
на ClientMaster
, если он был получен. В противном случае ничего не делайте и позвольте каскаду сохранить это значение.Используйте merge
вместо persist
для объекта ClientMaster
:
cmrepository.merge(cm);
Почему merge
a persist
будет нормально работать даже с существующими объектами ссылок.
Если вы извлечете объект Title.. он будет в отсоединенном состоянии, прежде чем сохранится. И если вы попытаетесь сохранить отдельный объект (через каскад здесь), вы получите исключение
Если Transactional не установлен на уровне контроллера. но этого мы не знаем
Большое спасибо, ребята, я разместил свой ответ ниже. Извините, я не могу пометить этот ответ как полезный, потому что у меня еще недостаточно репутации.
Я не думаю, что это хорошая идея, чтобы он не вставлялся, хотя, поскольку вы определили ключевое отношение forgien, всегда лучше, чтобы в таблице заголовков были сохранены все идентификаторы.
Нет, есть только предопределенный набор заголовков, которые инициализируются после создания таблицы. Если заголовок в запросе не совпадает ни с одним из существующих заголовков, то конечный пользователь не сможет вставить какую-либо запись в таблицу client_master. Это в основном требование.
Спасибо вам, ребята
Я последовал совету Мацея, и теперь мой контроллер выглядит так, как показано ниже.
@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);
}
Вместо CascadeType.ALL Попробуйте установить только MERGE