Страницы

Поиск по вопросам

среда, 18 декабря 2019 г.

Update сущность с полем updatable = false

#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 public S saveAndFlush(S entity) { S result = save(entity); flush(); return result; } В свою очередь метод save(entity) имеет такой вид: @Transactional public S 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()); } Но над вторым ответом на вопрос я еще подумаю...

Комментариев нет:

Отправить комментарий