| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Java EE (J2EE) и Spring > История Entity |
| Автор: ShurikA 6.1.2009, 12:39 |
| Мне нужно сохранять историю изменений Entities в таблицу истории. Пытаюсь сделать это через EntityListener - @PreUpdate, вроде по логике должно быть именно так. Но, дело в том что из нутри Entity нету инджекшен @PersistanceContext. Какое правильное решение для такого дела? |
| Автор: ShurikA 6.1.2009, 13:45 | ||||
Я уже думал об этом, но нужно именно копировать в другую таблицу. Добавлено через 2 минуты и 2 секунды powerOn, Кстати, а если сделать так как ты сказал. То каким образом задавать EntityManager-у условие подбора правильного Entity? Добавлено через 3 минуты и 1 секунду
И ещё: как бы это ни было, мне нужен @PersistanceContext. |
| Автор: powerOn 6.1.2009, 14:15 | ||
Через запрос в котором так же будет указана версия объекта. Вообще тут нужно конкретный use case рассмотреть. Возможно, какое-нибудь вообще другое решение подойдет... В Entity классе его автоматически не получить, поскольку они не управляются контейнером. Эти объекты ты создаешь сам, в отличии от EJB, которые для тебя создал и проинициализировал контейнер EJB. Вот контейнер создавая EJB позаботился о том, чтоб ссылка на EntityManager была поставленна корректно, а ты создавая свои entity нет. ;-) Получается что выход тут один (вернее два): 1) самостоятельно передавать ссылку на EntityManager в слушателя сущностей. Замечу, что разметить аннотациями @PreUpdate и др. можно не только саму сущность, но и другой класс, подключив его в последствии к сущности как слушателя через аннотацию @EntityListeners. 2) Сделать так что бы слушатель сущностей сам находил подходящего EntityManager-а. Например через JNDI lookup. |
| Автор: ShurikA 6.1.2009, 14:22 | ||
А это интересно... Может ли быть EJB слушателем? |
| Автор: powerOn 6.1.2009, 14:29 |
Как простой класс может, но его создавать будет не контейнер EJB, а значит и инжектировать EntityManager не получится... Оно и понятно, ведь в JavaSE приложениях тоже можно использовать JPA и создавать @EntityListeners, а там то не никакого EJB контейнера, все на плечи JPA имплементации положено... |
| Автор: MisterCleric 6.1.2009, 14:47 |
| Господа, может я и не по теме, но согласно пожеланиям автора темы такую задачу можно решить еще и таким простым способом: повесить в базе триггер на апдейт основной таблицы и ложить в другую таблицу копию измененной записи. А все остальное, соответственно, согласно логики приложения. Может триггеры и не помогут... |
| Автор: ShurikA 7.1.2009, 03:23 | ||
| У меня такая идея: Сделать Message Driven Bean который всего лиш будет принимать обьект Entity, причём не важно какой, и загонять его в базу. При этом, сам Entity, которому нужна история, будет создавать копию исторического Entity для самого себя, и отправлять в Bean. Добавлено через 1 минуту и 13 секунд
Есть что то такое? |
| Автор: ShurikA 7.1.2009, 10:11 | ||||||||
Всё таки всё перекрутил и решол делать как ты предложил. Получилось следующее:
но сразу появились пару вопросов: 1. В моём случае я не могу пользоваться точьной датой. Так что получается что вот это
не сработает. 2. Каким образом мапается этот Entity к Customer::type? На первый вопрос я вроде как могу ответить сказав что так как персистер пользуется методом equals(Object obj) что бы найти подходящий ключ, то это метод можно просто изменить так как нам нужно (дата должна быть не равна а между effective и expiration) и спокойно пользоваться em.find(CustomerType.class, new CustomerTypeId(id, new Date()));. Исправь если я не прав. А вот на тему второго у меня даже догадок никаких. |
| Автор: powerOn 7.1.2009, 16:40 |
| Что-то я плохо вопросы понял... Что сделать-то хочешь? Составной ключ (из числа и даты) и поиск по нему? Если Customer::type это как раз и есть составной ключ, то обычно его сущностью мапят с помощью @IdClass аннотации. |
| Автор: ShurikA 7.1.2009, 21:16 |
| powerOn, Дело не в этом. Когда составной ключь это 2 цифры которые должны совпадать, то всё понятно. В моём случае: id - должен совпадать, а дата должна быть >= effectiveDate. |
| Автор: powerOn 7.1.2009, 22:02 |
Что мешает на JPAQuery запрос написать? |
| Автор: ShurikA 7.1.2009, 23:09 | ||
Абсолютно ничего, тогда когда я хочу прото найти определённый обьект. А теперь преставь себе что есть Customer у которого есть relationship (соответственно ManyToOne со стороны Customer, так как нужен только последний effective CustomerType) В таком случае Customer должен выглядеть примерно вот так:
Как ты понимаешь, если делать mapping только по одной колонке, то вряд ли мне принесётся effectiveCustomerType. |
| Автор: ShurikA 8.1.2009, 09:54 |
| Есть ли в TopLink что то вроде @Where, как в Hibernate? |
| Автор: powerOn 8.1.2009, 11:39 |
Нет, Topllink реализует спецификацию JPA. Все что в спеке есть, все реализовано в Toplink (кроме Table per Concrete Class, ЕМНИП). Хотелось бы для начала понять отношение сущностей в твоей доменной модели... что нужно в результате получить... может тогда какое решение и придумается... |
| Автор: ShurikA 8.1.2009, 11:47 |
| OK, Есть два Еntity. (см. Picture 1.png). Customer упрощён. У Customer дольжен содержаться его тип, но только последний (я его назвал effectiveCustomerType), а не вся цепочка. effectiveCustomerType должен определяться таким образом: 1. Customer.customerTypeId == CustomerType.id 2. CustomerType.effectiveDate <= (настоящее время) 3. CustomerType.expirationDate == null; или > (настоящее время) Добавлено через 1 минуту и 6 секунд кстати, CustomerType.effectiveDate тоже PK. |
| Автор: MisterCleric 8.1.2009, 11:56 |
| И опять я вмешаюсь в вашу дискуссию. У меня что-то подобное есть, так опять таки такая задача решена на уровне базы. Все сущности хранятся в одной таблицы. Версии хранятся по принципу следующий-предыдущий + поле даты, когда была изменена сущность. Соответственно первая версия не имеет предшественника (форин кей на предудую версию), последняя версия не имеет "последовательника" (форин кей на следующую версию). Зачем что-то выдумывать в java на уровне ORM, если можно по простому на уровне базы, а в java просто реализовать логику реализации этой версионности |
| Автор: ShurikA 8.1.2009, 11:59 | ||
Что бы по минимому писать SQL запросы, и по максимому пользоваться тем что даёт mapping в JPA. Добавлено через 2 минуты и 23 секунды Более того, каким образом в моделе которую ты предлогаешь, будет сделан Mapping сущностей? Проблемма то таже. И она никак не в дата модели... |
| Автор: powerOn 8.1.2009, 12:04 | ||||
Если переносимость не важна, то можно и такие варианты рассмотреть. Правда процесс установки приложения будет немного сложнее за счет дополнительно конфигурации БД. Добавлено через 11 минут и 8 секунд
А что за цепочка? Я так понимаю, что каждый CustomerType должен быть связан с неким Customer, но при этом, каждый Customer имеет ссылку на эффективый, т.е. удовлетворяющий неким условиям, CustomerType? |
| Автор: ShurikA 8.1.2009, 12:21 | ||
Так условия вот такие:
И моя проблемма в том что мне нужно базируясь на настоящую дату пределить эти условия, так как на пример CustomerType.effectiveDate никогда уже не будет равен настоящей дате. Ну или опять же нужно найти способ получить последнюю версию, то есть так или иначе нужна проверка этой самой версии. Добавлено через 1 минуту и 15 секунд Как таковой однозначной ссылки на CuatomerType у Customer нету. |
| Автор: ShurikA 8.1.2009, 12:40 |
| В ресультате таблица ЦustomerType может выглядеть так: |
| Автор: powerOn 8.1.2009, 12:41 |
| Мне кажется нужно делать поиск CustomerType запросом. Без лишних мапингов. Просто пока не вижу причин извращаться. |
| Автор: ShurikA 8.1.2009, 12:44 |
Ну можно не извращаясь просто хранить историю в отдельной таблице, но тогда может усложниться репорт истории, особенно если таких связей много. Единственное что, для такого типа таблицы просто жалко делать ещё одну для истории. Добавлено через 49 секунд Ну или может ещё какие идеи будут? |
| Автор: ShurikA 8.1.2009, 13:08 |
| Или ещё проще: Вытаскивать все типы относящиеся к Customer, а логикой уже выберать тот который нужно. Но опять же это трата. |
| Автор: ShurikA 8.1.2009, 22:15 |
| Нашёл решение. Поделюсь как только отлажу. |
| Автор: powerOn 8.1.2009, 22:25 |
Отлично. Выкладывай. |
| Автор: MisterCleric 8.1.2009, 23:06 | ||
ага, мне тоже интересно... |
| Автор: ShurikA 9.1.2009, 04:06 | ||||||||||||||||||||||||
| Значит так. Я расскажу весь свой ход мыслей и к хему я пришёл в резултате. Не буду углубляться в саму проблемму (цм. выше), но суть в том что нужно организовать сохранение истории той или иной сущности при этом оставляя возможность relationship mapping (или поиск) по id сущностий Базовая требуемая сущность такова:
В JPA существует такая вещь как Inheritance при этом нескольких типов: - TABLE_PER_CLASS - JOINED - SINGLE_TABLE JOINED Вариант с JOINED я даже не буду разьяснять, так как модель молухается уж безсмысленно сложная и медленная. SINGLE_TABLE SINGLE_TABLE выглядет самый экономный и быстрый способ. для этого нужно добавить ещё одны колонку hist TINYINT(1) хто бы воспользоваться ей как discriminator по типу integer. создать два класса:
В классе CustomerTypeHist @Id должен задаваться а не генерироваться, потому что id CustomerTypeHist должен оставаться таким же как и в CustomerType, что бы Customer мог их всех найти. (! проблемма номер раз, нужно превратить ключ в Composit, добавив в него на пример effectiveDate ) А если такое дело ты нихего мы этим не добились и никак не воспользоваться тем что даёт JPA, а нужно гонять запрос ручками. Не хотим!!! Остаётся TABLE_PER_CLASS Во вот это то что нам нужно!!! делаем две таблисы:
Два класса:
Теперь Customer может смотреть на CustomerType у которого только одна возможная копия для него, а с историей мы уж как то разберёмся. Вроде как всё, но не тут то было: TABLE_PER_CLASS не поддержинается в TopLink!!! дасадно! *Кстати этот вариант я не проверял, так как работаю с TopLink. Если кто проверит расскажите. Ничего из вешеописанных не приблежается к решению. Но есть ещё вариант: нам не обязательно брать Inheritance от Entity. Делаем следующее: Таблицы:
И на этот раз 3 класса:
Дело в том что на этот раз мы берём только общую структуру у CustomerTypeSuper, а на таблицы завязываем уже сами сущности, которые друг с другом связаны только косвенно. в таблице CustomerType остаются по одной копии на каждый id тыпа (с одним PK). CustomerTypeHist recId превращается в PK, a по id мы можем найти всю историю изменений CustomerType с такимже id. Типичный результат таков: CustomerType
CustomerTypeHist
Ну вот вроде и всё. Теслировал через Bean. Добавлено через 4 минуты и 52 секунды А если не хочется писать по 3 класса на каждую сущность, то MappedSuperclass можно превратить во что то более абстрактное, что подходит для всех сущностей которым нужно сохранение истории, и пользоваться им где надо. P.S. Любые поправки и предложения с радостью принимаются. |
| Автор: ShurikA 9.1.2009, 09:50 |
| Осталось ещё кое что сделать: надо бы это дело автоматизировать. Я просто не нащёл путь сделать Dependency Injection в Entity, но идя в том что бы в @PreUpdate сущность сама записывала историю. |
| Автор: ShurikA 9.1.2009, 12:23 | ||
Как это сделать? |
| Автор: ShurikA 9.1.2009, 12:38 | ||||
вот таким образом это не работает:
Выдаёт:
|
| Автор: powerOn 9.1.2009, 19:02 | ||||
Самый простой в данной ситуации (когда ну просто никак не избежать доступа к БД из слушателя сущностей) способ это создать EJB компонент с @Remote интерфейсом, в который будет инжектироваться EntityManager. Из слушателя получать доступ к бину.
В коде слушателя:
Но! Мне такой подход не очень нравится. Прямо "Паблик Морозов" какой-то получился... никак не "сервис-локатор"... Поэтому я предлагаю общую логику работы с БД вынести в DaoBean и пусть он будет настоящим Dao сервисом. Добавлено через 3 минуты и 11 секунд Да, замечу, что доступ к EJB через JNDI весьма зависит от сервера приложений что вы используете. Данный вариант был описан для Glassfish. ЕМНИП, на JBoss необязательно делать бин с удаленным интрефейсом, можно и до локального простым лукапом достучаться. |
| Автор: ShurikA 10.1.2009, 02:17 |
| powerOn, Кстати, есть какая то причина что PreUpdate не вызывается если он прописан в MappedSuperclass, а не в самой сущности? |
| Автор: powerOn 10.1.2009, 13:27 | ||
В спеке написано что можно и в mapped superclass слушателей определять. Давай код что-ли посмотрим... |
| Автор: ShurikA 11.1.2009, 07:08 | ||
Я где то выкопал что это баг. Сейчас не могу найти где? Добавлено через 1 минуту и 57 секунд Но на самом деле он мне в суперклассе не очень нужен. Можно было бы эту функцию сделать generic абсолютно для всех, но она будет немногим медленная. |
| Автор: sandello 11.1.2009, 12:01 |
| Похожую, если не именно эту, задачу решает проект http://www.jboss.org/envers. Правда, он обладает кучей ограничений. К примеру, заточен под hibernate |
| Автор: ShurikA 12.1.2009, 12:39 | ||||||
Воткнулся в очередную проблемму:
При этом CustomerType выглядет так:
|
| Автор: ShurikA 12.1.2009, 14:27 |
| Если анотацию @PreUpdate убрать, то всё ОК. |
| Автор: powerOn 13.1.2009, 09:55 |
| все аннотации либо всегда указывают на методах, либо всегда на полях. т.е. изначально выбирается один стиль. в твоем случае нужно указывать на методах, поскольку @PreUpdate на поле нельзя указать. |
| Автор: ShurikA 13.1.2009, 10:04 |
| powerOn, То есть мне нужно все мои анотации перенести на методы? Это достаточно сделать только в одном классе? |
| Автор: powerOn 13.1.2009, 10:07 | ||
Попробуй. Если будет не достаточно, то значит во всех. |
| Автор: ShurikA 14.1.2009, 03:22 |
| если класс(Entity) порождается от папы а папа в свою очередь от его папы, и папа и дедушка должны быть @MappedSuperclass ? |
| Автор: ShurikA 14.1.2009, 10:05 | ||
OK, вроде всё работает и вызывается. Следуюшая проблемма выглядит так как будто @PreUpdate вызывается после того как поля уже поменянны, как можно отловить момент до того как они меняются? |
| Автор: powerOn 14.1.2009, 12:18 | ||
Не понял. PreUpdate вызывается перед обновлением данных в БД. |
| Автор: ShurikA 14.1.2009, 21:17 |
Правильно, но поля обьекта уже изменены к тому времени. |
| Автор: powerOn 14.1.2009, 22:37 |
Ну это уже не в компетенции JPA следить за такими вещами, хотя оно и понимает что объект нужно обновить в БД... В остальном, думаю, нужно разрабатывать собственное решение. |
| Автор: chand0s 15.1.2009, 14:36 |
| Уже говорили, но если вы используете hibernate-entitymanager - таки посмотрите на проект envers. Он делает как раз то что вам надо - вешается на PostInsert, PostUpdate, PostDelete события и протоколирует изменение выбранных entities. Единственный минус - к аннотациям @Entity и @Table прибавляется еще одна - @Audited, т.е. envers всегда должен будет быть хотя-бы в classpath. С другой стороны - скоро envers будет модулем hibernate и так или иначе будет в classpath. |
| Автор: powerOn 15.1.2009, 19:44 | ||
используется Toplink. |