#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()); } Но над вторым ответом на вопрос я еще подумаю...
Комментариев нет:
Отправить комментарий