Как координировать доступ к базам данных J2EE и Java EE?

У нас есть довольно большое приложение, которое началось десять лет назад и все еще находится в стадии активной разработки. Таким образом, некоторые части все еще находятся в архитектуре J2EE 1.4, другие используют Java EE 5/6.

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

Планируется полностью перенести старый код, чтобы избавиться от J2EE, а пока — что я могу сделать, чтобы скоординировать доступ к базе данных между двумя частями? И не должны ли в какой-то момент на сервере приложений оба пути объединиться на уровне Hibernate, независимо от того, осуществляется ли доступ через JPA или напрямую?


person Alexander Rühl    schedule 30.06.2014    source источник
comment
Я бы предложил вам ввести блокировку базы данных вместо изоляции на уровне транзакций сеанса, поскольку они управляются разными соединениями. что-то вроде @Version может помочь   -  person maress    schedule 30.06.2014
comment
Хм, интересно, в этом случае ответ заключается в использовании распределенных транзакций, а не в блокировке. Это немного расплывчато, но я понимаю, что проблема в том, что старый код и новый код без необходимости работают в изолированных транзакциях и на самом деле должны работать в одной и той же транзакции.   -  person Gimby    schedule 30.06.2014
comment
@Gimby: Чтобы уточнить, информация, поступающая и обрабатываемая в старой и новой частях, представляет собой отдельные варианты использования - мне нужен был бы только общий контекст сохранения для обоих, чтобы обе части видели одни и те же данные.   -  person Alexander Rühl    schedule 03.07.2014
comment
@maress: у меня есть столбец версии в базе данных, используемый Entity. Так что же нужно сделать, чтобы использовать то же поле в старом J2EE DAO?   -  person Alexander Rühl    schedule 03.07.2014
comment
Я предполагаю, что в старом J2EE DAO вам может понадобиться какой-то общий способ непосредственно перед сохранением или обновлением, чтобы подтвердить, что значение столбца для поля @Version не изменилось. Если вы посмотрите на спящий режим (я действительно не могу сейчас сказать, в какой момент), но он должен быть далеко, вы можете подключить некоторые перехватчики перед сохранением/обновлением и подтвердить, что поле не изменилось   -  person maress    schedule 03.07.2014
comment
Если у вас есть централизованный способ получить сеанс в старом коде, вы можете просто изменить его и извлечь сеанс гибернации из EntityManager (или SessionFactory из EntityManagerFactory).   -  person Serge Ballesta    schedule 30.07.2014
comment
Как вы получаете доступ к сеансу гибернации в старом коде?   -  person Serge Ballesta    schedule 30.07.2014
comment
Вы поставляете Hibernate вместе с приложением или используете Hibernate со своего сервера приложений?   -  person Philippe Marschall    schedule 30.07.2014
comment
Ваша проблема в том, что вы пытаетесь зафиксировать две несвязанные записи, и каждая из них получает тот же (сгенерированный) порядковый номер, что и их первичный ключ, когда они должны получать уникальные значения? Или проблема заключается в том, что два фрагмента кода пытаются зафиксировать запись для одной и той же сущности, потому что она не существует ни в одном из их сеансов при запуске, но затем она отклоняется как повторяющаяся сущность, как только они оба фиксируют?   -  person Tim    schedule 30.07.2014
comment
@ Тим: последний.   -  person Alexander Rühl    schedule 31.07.2014
comment
@PhilippeMarschall: Будет ли это иметь значение? Я не могу проверить в данный момент, так как я вне офиса, но я предполагаю, что это реализация Hibernate JBOss.   -  person Alexander Rühl    schedule 31.07.2014
comment
@SergeBallesta: Как уже говорилось ранее, я не могу проверить прямо сейчас, но, насколько я помню, мы получим его из SessionFactory.   -  person Alexander Rühl    schedule 31.07.2014
comment
@Geziefer, не могли бы вы показать нам, как вы получаете SessionFactory и EntityManager (также покажите нам файлы xml). Я предполагаю, что у вас есть эта проблема с определенными объектами и определенными вариантами использования. Учитывая это, пытались ли вы переключить обе части (старую и новую) одинаковым образом (либо EntityManager, либо SessionFactory)?   -  person Andrei I    schedule 31.07.2014
comment
@AndreiI: Я сделаю это, когда вернусь в офис. Я еще не пробовал что-то переключать, так как это сопоставимое сложное приложение, и, пытаясь найти возможные решения проблемы, я также пытаюсь оценить затраты на реализацию этого временного решения по сравнению с переносом старого кода, который должен быть сделан раньше или позже.   -  person Alexander Rühl    schedule 31.07.2014
comment
@Geziefer да, если реализация JPA отличается от Hibernate (например, JPA с сервера, но Hibernate из приложения), это объясняет ваши проблемы.   -  person Philippe Marschall    schedule 31.07.2014
comment
@PhilippeMarschall: я проверил, что мы не поставляем Hibernate, а используем тот, который поставляется из JBoss 7.   -  person Alexander Rühl    schedule 04.08.2014
comment
@SergeBallesta: я проверил, что мы получаем org.hibernate.SessionFactory, вызывая org.hibernate.cfg.Configuration.buildSessionFactory(), а затем org.hibernate.Session, вызывая mySessionFactoryImpl.getCurrentSession().   -  person Alexander Rühl    schedule 04.08.2014
comment
@AndreiI: см. ответ на ваш комментарий выше. EntityManager вводится через @PersistenceContext. Какие части XML могут быть дополнительно интересны?   -  person Alexander Rühl    schedule 04.08.2014
comment
@Geziefer: Если есть возможность заменить вызов buildSessionFactory, см. мой ответ ниже.   -  person Serge Ballesta    schedule 04.08.2014
comment
Используете ли вы стандартное сохранение JavaEE? Содержит ли развертывание вашего приложения собственную копию Hibernate? Используете ли вы менеджер транзакций JTA в конфигурации Hibernate? Как осуществляется демаркация транзакций? Используете ли вы сеансовые компоненты (возможно, без сохранения состояния) для этой цели?   -  person Steve C    schedule 04.08.2014
comment
@SteveC: в новой части используется JPA 1.0, в старой — Hibernate. Как было сказано, мы не предоставляем Hibernate, он взят из JBoss 7. В старых частях транзакции выполняются вручную, но с использованием JTA контейнера. В новой части все делает контейнер. EJB используются в обоих случаях.   -  person Alexander Rühl    schedule 05.08.2014


Ответы (3)


Если вы создаете отдельно SessionFactory для старого кода и EntityManagerFactory для нового кода, это может привести к разным значениям в кеше первого уровня. Если во время одного Http-запроса вы измените значение в старом коде, но не сразу зафиксируете, значение будет изменено в кэше сеанса, но не будет доступно для нового кода, пока он не будет зафиксирован. Независимо от любой блокировки транзакции или базы данных, которая защитит постоянные значения, это сочетание двух разных сеансов Hibernate может привести к странным вещам для значений в памяти.

Я признаю, что внедренный EntityManager все еще использует Hibernate. ИМХО, самое надежное решение - получить EntityManagerFactory для PersistenceUnit и перевести его в Hibernate EntityManagerFactoryImpl. Затем вы можете напрямую получить доступ к базовому SessionFactory :

SessionFactory sessionFactory = entityManagerFactory.getSessionFactory();

Затем вы можете безопасно использовать этот SessionFactory в своем старом коде, потому что теперь он уникален в вашем приложении и используется как в старом, так и в новом коде.

Вам все равно придется решать проблему создания-закрытия сеанса и управления транзакциями. Я полагаю, что это уже реализовано в старом коде. Не зная больше, я думаю, что вам следует перенести его на JPA, потому что я почти уверен, что если EntityManager существует, sessionFactory.getCurrentSession() даст лежащий в его основе Session, но я не могу утверждать ничего об обратном.

person Serge Ballesta    schedule 30.07.2014
comment
Из всех комментариев я получил некоторые полезные идеи, но я оцениваю это предложение лучше всего и посмотрю, как с ним работать. Как я уже сказал, в будущем планируется полностью избавиться от частей J2EE и напрямую использовать Hibernate, но пока это может быть полезно. - person Alexander Rühl; 05.08.2014

Вы можете без проблем смешивать Hibernate Session и Entity Manager в одном приложении. EntityManagerImpl просто делегирует вызывает частный экземпляр SessionImpl .

То, что вы описываете, является аномалией конфигурации транзакции. Каждая транзакция базы данных выполняется изолированно (если вы не используете REAN_UNCOMMITED, что, я думаю, не так), но как только вы ее зафиксируете, изменения будут доступны из любой другой транзакции или соединения. Поэтому, как только транзакция зафиксирована, вы должны увидеть все изменения в любом другом сеансе Hibernate, соединении JDBC или даже в вашем инструменте управления пользовательским интерфейсом базы данных.

Вы сказали, что произошел конфликт первичного ключа. Этого не может произойти, если вы используете Hibernate Identity или генератор последовательности. Для старого генератора Hi-Lo у вас могут возникнуть проблемы, если внешнее соединение попытается вставить записи в ту же таблицу Hibernate использует старый генератор идентификаторов hi/lo.

Эта проблема также может возникнуть при наличии аномалии репликации мастер/мастер. Если у вас есть несколько узлов и нет репликации строгой согласованности, вы можете столкнуться с нарушениями ограничений первичного ключа.

Обновить

Решение 1:

При координации нового и старого кода, пытающегося вставить один и тот же объект, вы можете иметь логику выбора, чем вставки, работающую в транзакции SERIALIZABLE. Транзакция SERIALIZABLE получает соответствующие блокировки от имени тура, поэтому вы все еще можете иметь уровень изоляции READ_COMMITTED по умолчанию, в то время как только проблемные методы службы помечены как SERIALIZABLE.

Таким образом, как в старом коде, так и в новом коде эта логика запускает выборку для проверки, существует ли уже строка, удовлетворяющая ограничению выбора, только для ее вставки, если ничего не найдено. Уровень изоляции SERIALIZABLE предотвращает фантомное чтение, поэтому я думаю, что он должен предотвращать нарушения ограничений.

Решение 2:

Если вы готовы делегировать эту задачу JDBC, вы также можете изучить оператор MERGE SQL, если ваша текущая база данных поддерживает это. По сути, это операция upsert, выдающая обновление или вставку за кулисами. Эта команда намного привлекательнее, поскольку вы можете запускать ее даже при READ_COMMITTED. Единственным недостатком является то, что вы не можете использовать Hibernate для этого, и только некоторые базы данных поддерживают его.

person Vlad Mihalcea    schedule 30.07.2014
comment
Вы правы, как только он зафиксирован, но я думаю, что проблема в том, что каждый из двух разных компонентов ORM проверяет существующую запись с заданным идентификатором практически в одно и то же время, и поскольку ни один из них не найден пытается вставить новую запись, что приводит к нарушению PK для одной из них. - person Alexander Rühl; 30.07.2014
comment
Какой генератор идентификаторов вы используете? - person Vlad Mihalcea; 30.07.2014
comment
Я использую это в объекте (Java EE): @GenericGenerator(name="abc", strategy="sequence", parameters = {@Parameter(name="sequence", value="xyz")}) @GeneratedValue(strategy = GenerationType.AUTO, generator = "abc") - person Alexander Rühl; 30.07.2014
comment
А это в файле сопоставления .hbm.xml (J2EE): <id name="id" type="long"> <column name="id" /> <generator class="sequence"> <param name="sequence">xyz</param> </generator> </id> - person Alexander Rühl; 30.07.2014
comment
Поскольку вы используете GenericGenerator, вы фактически можете указать на фактический генератор идентификаторов Hibernate. Генератор последовательности всегда обращается к базе данных за новым значением идентификатора, поэтому конфликт не может произойти, если у вас нет нескольких узлов и сбоя репликации. Последовательность базы данных обеспечивает уникальность значения, поэтому Hibernate не подводит вас. - person Vlad Mihalcea; 30.07.2014
comment
Понятно, возможно, это происходит из-за того, что раньше существовала комбинация столбцов, которые вместе образовывали объединенный первичный ключ и которые теперь заменены техническим идентификатором и проверкой ограничения для него, который все еще может иметь вводящее в заблуждение имя pk_ ... Но все же проблема в том, что на шаг назад оба компонента пытаются вставить, думая, что запись еще не сохранена. И поэтому я предположил, что об этом позаботится один централизованный менеджер объекта, или я ошибаюсь? - person Alexander Rühl; 31.07.2014
comment
Менеджер объектов делегирует эту ответственность генераторам идентификаторов. Если у вас все еще работают оба, обеспечение уникальности должно быть делегировано БД. Так всегда безопаснее. - person Vlad Mihalcea; 31.07.2014
comment
@VladMihalcea, судя по комментариям ОП, это не связано с генерацией идентификатора, это связано с тем, что отдельные сеансы пытаются одновременно сохранить один и тот же объект. Речь идет об уникальных ограничениях на естественные ключи, не генерирующих идентичные искусственные идентификаторы. - person Tim; 31.07.2014
comment
Я немного озадачен сейчас. В вопросе говорилось о нарушении ограничения первичного ключа. Hibernate естественный ключ имеет больше логическое значение бизнес-ключа, но не играет роли в назначении первичного ключа идентификатора. - person Vlad Mihalcea; 04.08.2014
comment
@VladMihalcea: Может быть, я не совсем понял здесь - в старом коде таблица имела комбинированный первичный ключ, затем был введен технический ПК, но все же комбинация полей, ранее определенных как ПК, должна быть уникальной. - person Alexander Rühl; 04.08.2014
comment
Я добавил обновление. Надеюсь, поможет. В основном вы можете использовать SERIALIZABLE или MERGE. - person Vlad Mihalcea; 04.08.2014
comment
Одна вещь, которую следует учитывать, если вы пойдете по пути использования SERIALIZABLE: вы будете удерживать блокировку на время транзакции, что будет блокировать другие потоки до тех пор, пока транзакция не будет зафиксирована. Если вы пойдете по этому пути, убедитесь, что вы минимизируете время, затрачиваемое на транзакцию, сделав все возможное до ее запуска, чтобы свести к минимуму влияние на производительность. - person Tim; 04.08.2014
comment
Я думаю, что у каждого из этих решений есть свой большой недостаток: 1. Непросто (нестандартный способ) установить уровень изоляции транзакций в EJB (по сравнению с Spring). Также использование SERIALIZABLE может привести к другим проблемам с производительностью. 2. Я действительно не советую работать на уровне JDBC, так как вы, вероятно, потратите свое время, частично изобретая колесо. - person Andrei I; 05.08.2014
comment
@ Влад, я действительно не хочу быть грубым, я просто считаю эти решения простыми идеями, которые не помогут решить проблему ОП. 1) В качестве примера: не могли бы вы привести пример того, как установить уровень транзакции для определенного метода в JBOSS/Wildfly AS? 2) Второе решение касается JDBC, а не Hibernate. Вы можете добавить третье решение со стеком знаний о данных. - person Andrei I; 05.08.2014
comment
@VladMihalcea Я много лет работаю с JBoss и не знаю никаких решений для установки уровня изоляции транзакций, кроме как на уровне соединения. Поэтому, возможно, вам следует указать, что некоторые серверы приложений поддерживают это, но, возможно, не все. Также я МОЖЕТ БЫТЬ приду с решением, но я боюсь, что ОП не предоставил НИКАКИХ подробностей о том, как выполняется инъекция, распространение транзакций, конфигурация. - person Andrei I; 05.08.2014
comment
Еще во времена J2EE, когда я разрабатывал приложение, работающее на JBoss, раньше существовал специальный дескриптор ejb xml, в котором свойства транзакции могли быть переопределены для каждого метода EJB. В качестве альтернативы вы можете использовать BMT и установить: connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); перед началом транзакции и сбросить его до предыдущего значения прямо перед существующим. - person Vlad Mihalcea; 05.08.2014

Я столкнулся с аналогичной проблемой, когда у меня был список перечисляемых значений поиска, где два фрагмента кода проверяли наличие заданного значения в списке, и если оно не существовало, код создавал новую запись в базе данных. Когда они оба сталкивались с одним и тем же несуществующим значением, они оба пытались создать новое, и транзакция одного из них откатывалась (отбрасывая кучу другой работы, которую мы сделали в транзакции).

Наше решение состояло в том, чтобы создать эти поисковые значения в отдельной транзакции, которая была зафиксирована немедленно; если эта транзакция прошла успешно, то мы знали, что можем использовать этот объект, а если она не удалась, то мы знали, что нам просто нужно выполнить get, чтобы получить объект, сохраненный другим процессом. Как только у нас появился объект поиска, который, как мы знали, можно было безопасно использовать в нашем сеансе, мы могли с радостью выполнить остальные модификации БД, не рискуя откатить транзакцию.

Из вашего описания трудно понять, подходит ли ваша модель данных для аналогичного подхода, когда вы, по крайней мере, сразу фиксируете начальную версию объекта, а затем, когда вы уверены, что работаете с постоянным объектом вы могли бы сделать остальные модификации БД, которые, как вы знали, вам нужно было сделать. Но если вы сможете найти способ заставить это работать, это позволит избежать необходимости совместного использования сеанса между различными фрагментами кода (и будет работать, даже если старый и новый код выполняются в разных JVM).

person Tim    schedule 30.07.2014