Непонятное поведение JPA/Hibernate

t1h0n0ff

Допустим есть 2 сущности
public class Parent {
long id;
List<Child> children;
}

public class Child {
long id;
Parent parent;
String category;
}

Допустим в одной транзакции я делаю запрос дважды (jpql):
 select p from Parent p
join fetch p.children c
where p.id = :id and c.category = :category

1 запуск: id = 3, category = 'category_1'
2 запуск: id = 3, category = 'category_2'
Почему при втором запуске в children попадают Child с 'category_1'?
Я понимаю что они кешируются, но зачем?
Может кто подскажет другой метод фильтра children?
Использовался hibernate-entitymanager/4.3.0.Final
Спасибо.

6yrop

при втором запуске в children попадают Child с 'category_1'?
а при первом в children попадают Child с 'category_2'?

6yrop

Может кто подскажет другой метод фильтра children?
jdbc

psm-home

Посмотри SQL, который улетает в базу, м. б там чего интересное будет.
А вообще, ты выбираешь объекты Parent по некоторому критерию, тебе их возвращают. При этом фильтровать в получившихся объектах дочернюю коллекцию никто не обещал. Добро пожаловать в ORM, мир протекающих абстракций, vietnam of computer science, все такое.

t1h0n0ff

Нет, при первом выборе все ок, children подставляется по нужно категории.

t1h0n0ff

Я понимаю, что подход слегка нестандартный, но в контексте задачи он представлял самое локаничное и простое решение.

t1h0n0ff

Если сделать show_sql=true, то видно что при запросе 1 - он делает 2 запроса в БД, выбирая первым Parent, вторым Child. При втором тоже самое. То есть влогах 4 записи, но при втором все равно отдает кеш первого запроса :(

psm-home

выбирая первым Parent, вторым Child
Во втором запросе есть фильтр c.category = :category ?

6yrop

То есть влогах 4 записи, но при втором все равно отдает кеш первого запроса
А что ты хотел, может ты уже сам поменял содержание листа List<Child> children, хибернейт же об этом не знает, он просто отдает тебе экзеплеры Parent из Identity Map.
Мне удивительно почему от первый раз накладывает фильтр на children. Спасибо за хороший пример, демонстрирующий дырявость абстракции ORM.

t1h0n0ff

Запросы асболютно одинаковые, различается только category.

Hastya

А что именно ты хочешь выбрать? Твой запрос с точки зрения hibernate не очень правильный - т.е. возможно на самом деле ты хочешь сделать select children join Parent?

serega1604

по-моему аболютно понятное поведение, если вспомнить про session cache и как он работает.

6yrop

если вспомнить про session cache и как он работает
человеческое мышление надо экономить

serega1604

человеческое мышление надо экономить
я вижу, что ты успешно его наэкономил уже.

6yrop

да, а твоих успехов не видно :(

katrin2201

Почему при втором запуске в children попадают Child с 'category_1'?
Я понимаю что они кешируются, но зачем?
Может кто подскажет другой метод фильтра children?
Ничего не кешируется. Поведение by design. Как сказали выше, твой запрос означает выбрать всех парентов, у которых есть хотя бы один чайлд удовлетворяющий запросу. И полученные паренты - это ентити, то есть отображение на всю реляцию целиком, без твоих условий на категорию.
Вот тут объяснено подробно с двумя вариантами решений.

6yrop

Поведение by design.
Почему нет симметрии относительно category_1 и category_2? Причем симметрию нарушает только порядок выполнения запросов. Почему получатся разные результаты, если запросы выполнять вот в таком порядке
1. category = 'category_1'
2. category = 'category_2'
или в таком
1. category = 'category_2'
2. category = 'category_1'
?
Согласись, довольно странный design.

6yrop

Нет, при первом выборе все ок, children подставляется по нужно категории.
Есть подозрение, что ты обманываешь, при симметрично наполнении базы, запросы будут выдавать аналогичные результаты.

t1h0n0ff

Я не обманываю :) Это точно.

psm-home

Упоролся и набросал минимальный тестовый пример. Описанное тобой поведение не воспроизвелось. Что я сделал не так?

katrin2201

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

t1h0n0ff

По твоим результатам ни в первом ни во втором случаи фильтрации по category не было.
Query by category_1 returns:[Parent{id=1, children=[Child{id=1, parent.id=1, category='category_1'}, Child{id=2, parent.id=1, category='category_2'}]}]
Query by category_2 returns:[Parent{id=1, children=[Child{id=1, parent.id=1, category='category_1'}, Child{id=2, parent.id=1, category='category_2'}]}]
Или я чего-то не вижу?
Должно быть вот так:
Query by category_1 returns:[Parent{id=1, children=[Child{id=1, parent.id=1, category='category_1'}]}]
Query by category_2 returns:[Parent{id=1, children=[Child{id=2, parent.id=1, category='category_2'}]}]

psm-home

Не было, да. И не должно было быть, если я правильно понимаю, как работает JPA/Hibernate. А ты описал поведение, когда она то есть, то нет. Или я тебя не понял?

6yrop

По твоим результатам ни в первом ни во втором случаи фильтрации по category не было.
Ты тут всех запутал, написав:
Нет, при первом выборе все ок, children подставляется по нужно категории.

Короче, Хиберней вполне ожидаемо себя ведет, просто вот это условие "c.category = :category" означает EXISTS. Это не совсем очевидно, но прежде чем написать такое выражение нужно было поинтересоваться что оно значит, у меня бы такой вопрос явно возник.

t1h0n0ff

Я еще раз напишу. Я делаю 2 запроса, в первом category_1, во втором category_2.
Делаю запрос первый раз с параметром category_1, в результате я получаю нужный Parent и с Children заполненный правильно, то есть там все Child, у которых category = category_1.
В этой же сессии я делаю второй запрос с параметром category_2, в результате получаю children в которых присутствуют только Child, у которых category = category_1 (ПОЧЕМУ?).
Вроде доступно написано.
когда я пишу join fetch - я подразумеваю что мне нужно зафетчить то, что я скажу, то есть фильтрованные по category, а этого не происходит.
Вот и напрашивается почему вроде логичное выражение выводи нелогичный результат.

6yrop

Делаю запрос первый раз с параметром category_1, в результате я получаю нужный Parent и с Children заполненный правильно, то есть там все Child, у которых category = category_1.
добавь в свою базу данные так, чтобы у этого Parant было два Child c category_1 и category_2. И твой фильтр не будет работать и в ПЕРВОМ запросе.

katrin2201

когда я пишу join fetch - я подразумеваю что мне нужно зафетчить то, что я скажу, то есть фильтрованные по category, а этого не происходит.
join fetch - просто говорит заселектить в том же запросе еще и ту коллекцию чайлдов. Про фильтрацию - это ты сам додумал.

t1h0n0ff

Разве не логично, если я пишу join fetch p.children c - мне позволяется так писать. Зачем я в блоке where могу использовать "c" для фильтрации. По вашему для чего это позволяют делать?

t1h0n0ff

С самого начала эти данные присутствовали.

t1h0n0ff

В твоем случае нужно было вместо "join fetch" написать "inner join fetch".
Накидал проект, который воспроизводит поведение, и выложил его на GitHub.

katrin2201

Пофигу, что join fetch что inner join fetch все равно:
A fetch join does not usually need to assign an alias, because the associated objects should not be used in the where clause (or any other clause).
Сочувствую.
Воркэраунды - юзать фильтры (@Where выключать кеш для кривой квири, сливать кеш после нее.
http://java-persistence-performance.blogspot.com/2012/04/obj...

t1h0n0ff

Если не юзать inner, то при запросах ответ будет одинаковый и филтрации по category ни в первом ни во втором случае не будет.

katrin2201

У меня ответ всегда одинаковый (обе квири возвращают один и тот же инстанс Парента и если есть fetch то в чилдренах одна категория. Если fetch убрать - то категорий две.

Hastya

Если не юзать inner, то при запросах ответ будет одинаковый и филтрации по category ни в первом ни во втором случае не будет.
У тебя выборка select Parent, и результаты выборки не Child, а Parent. Фильтрация тут не имеет особого смысла, т.к. семантика parent.getChildren - вернуть всю коллекцию целиком. Тебе нужно переделать запрос, например, сделать его many-to-one, а не one-to-many

t1h0n0ff

Ошибся. Извиняюсь.

katrin2201

Ноу проблем. Я тоже про fetch не верил, а оно вон оно как.
Понятно, конечно, что у фреймоврка нет вариантов эффективно прожевать такой запрос... С другой стороны могли бы просто автоматом кеш отключать для таких запросов. Или хотя бы ошибку выдавать... А такое сломанное поведение замалчивать совсем нехорошо...
Оставить комментарий
Имя или ник:
Комментарий: