#java #hibernate #orm #jpa #spring_data
Использую JPA + Spring Data + Hibernate для записи данных. Есть сервис с вот таким
методом (реализация JPA репозитория стандартная):
@Transactional
public EmployeeJpaBean create(final EmployeeJpaBean employeeJpaBean) {
return employeeJpaRepository.saveAndFlush(employeeJpaBean);
}
Сущность EmployeeJpaBean имеет такое вот поле:
@Column(name = "empl_name", unique = true, nullable = false, updatable = false)
private String name;
Как видно - оно не должно быть updatable.
Проверяю это в юнит тесте вот так:
@Test
public void updateNameTest() {
EmployeeJpaBean employeeJpaBean = new EmployeeJpaBean();
employeeJpaBean.setName("old");
EmployeeJpaBean saved = employeeService.create(employeeJpaBean); //1
Long id = saved.getId();
saved.setName("new name");
saved = employeeService.create(employeeJpaBean); //2
//падает Assert.assertEquals("old", saved.getName());
EmployeeJpaBean read = employeeService.read(id); //3
Assert.assertEquals("old", read.getName());
}
На закомментированной строке Assertion падает. Глянул что пишет Hibernate - при выполнении
строк кода помеченными 2 и 3 - выполняется один и тот же select, но результат на выходе
разный.
Во первых, почему разный результат select'ов?
а во вторых - как сделать так, чтобы неверные данные не возвращались из метода saveAndFlush
? (ведь поле не updatable).
Ответы
Ответ 1
В документации сказано, что updatable=false влияет только на генерацию UPDATE запросов. В строке (1) мы создаем новую сущность и у нас должен генерироваться SQL INSERT запрос. В строке (2) мы вызываем метод employeeJpaRepository.saveAndFlush, который по документации (исходный код) должен вызвать save(entity) and flush(). @Transactional publicS saveAndFlush(S entity) { S result = save(entity); flush(); return result; } В свою очередь метод save(entity) имеет такой вид: @Transactional publicS save(S entity) { if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } } Т.к. объект у нас уже существует, то будет вызван метод em.merge(entity); Идем в описание метода merge. Обращаемся к JSR-220 (вот ссылка на pdf, но возможно, чтобы открыть надо принять лицензионное соглашение). Идем в секцию: 3.2.4.1 Merging Detached Entity State The semantics of the merge operation applied to an entity X are as follows: ... ... If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been anno- tated with the cascade element value cascade=MERGE or cascade=ALL annotation. Более простой вариант из интернета: if the entity is already in the persistence context (session), no action is taken, except for cascades Вольный перевод: Если Х это managed сущность, то она будет проигнорирована при merge операции, но она будет применена к каскадно связанным сущностям... Но почему же у нас все-таки идет SELECT в строке (2)? Идет запрос за "самой последней" версией нашей сущности, чтобы потом механизмом dirty checking мы нашли изменения и смогли сгенерировать UPDATE query. Подытоживая все вышесказанное и отвечая на вопросы: Результат обоих запросов SELECT одинаковый, но просто при merge возвращается уже существующий объект из сессии, в котором уже изменено значение name. Сходу могу предложить модифицировать метод таким образом @Transactional public EmployeeJpaBean create(final EmployeeJpaBean employeeJpaBean) { employeeJpaRepository.saveAndFlush(employeeJpaBean); return employeeService.read(employeeJpaBean.getId()); } Но над вторым ответом на вопрос я еще подумаю...
Комментариев нет:
Отправить комментарий