Подтвердить что ты не робот

Hibernate + Swing

Какая лучшая практика?

  • Один сеанс для всего приложения
  • Сеанс за окно
  • Сеанс в потоке и случайное отключение
  • Что-то еще?

Я искал googled вокруг, и нет никакого консенсуса. Все говорят что-то, а затем забирают обратно. Я нахожу таскать сеанс вокруг глупых...

Итак, что сделка?

4b9b3361

Ответ 1

Может быть, сеанс для обработки событий - правильный подход. В веб-приложениях мы обычно создаем один сеанс для каждого запроса (используя все эти материалы OpenSessionInView и т.д.). Если мы проследим, каждый запрос в веб-приложении - это другое взаимодействие в приложении, а в приложении Swing каждое событие, инициированное каждым событием, представляет собой другое взаимодействие в приложении. Если мы применяем те же принципы, которые применяются в веб-приложениях в приложениях Swing, тогда мы должны создать сеанс для обработки событий.

Но мы должны определить, является ли использование Hibernate лучшим вариантом... База данных, к которой вы обращаетесь, является локальной или удаленной базой данных? У других приложений есть одна и та же база данных? Вы должны подумать о создании бэкэнда EJB\HTTP вместо того, чтобы ваши приложения Swing напрямую обращались к базе данных. Использование Hibernate предполагает, что у вас есть не очень простая база данных, поэтому я думаю, вам стоит подумать о создании бэкэнда EJB\HTTP.

Ответ 2

Я использовал Hibernate в корпоративном размере Rich Internet Application, которое по методике напоминает много сочетаний спящего режима + свинг. Я потратил довольно много времени на изучение различных шаблонов проектирования для использования спящего режима в трехуровневом приложении.

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

Это не идеальное решение, так как вы столкнетесь с некоторыми проблемами, например, с ленивой загрузкой и транзакциями. Я кратко объясню эти проблемы и их решение.

Сделки
Поскольку вы завершаете сеанс гибернации после каждого запроса, вы не можете иметь транзакции, которые живут за пределами одного запроса. Это может быть несколько проблематично, например, скажем, вы хотите сохранить 10 объектов в рамках одной и той же транзакции. Вы не можете сделать запрос сохранения отдельно для каждого объекта, так как транзакция завершается. Поэтому вам нужно сделать метод, который принимает в качестве входного списка список объектов и сохраняет все те объекты в рамках одной и той же транзакции. Если транзакция завершилась неудачно, вы откатите все объекты.

Ленивая загрузка
Ложная загрузка ваших объектов не будет работать, потому что они, скорее всего, не привязаны к сеансу (то есть, если вы лените нагрузку после завершения сеанса). Для ленивой загрузки для работы вам нужно повторно привязать объекты к сеансу. Я придумал обходной путь для этого, что довольно сложно при создании новых объектов сущностей, но прекрасно работает в повседневном развитии. Идея состоит в том, чтобы иметь дублированные геттеры и сеттеры для поля, которое лениво загружается. Одна пара является частной, а другая публичной. Идея состоит в том, что частная пара getter/setter - это то, что hibernate использует внутри себя, а публичные геттеры и сеттеры являются переходными и используются разработчиком. Итак, что происходит, когда разработчик вызывает публичный getter, геттер проверяет, было ли поле уже загружено, а если нет, присоедините объект к сеансу, загрузите поле и закройте сеанс. Войла, все работает, и разработчик ничего не заметил. Вот небольшой код, который даст вам идею:


@Entity
public class Foo {
  List<Bar> bars = new ArrayList<Bar>();

  @OneToMany
  private List<Bar> getBarsInternal() {
    return bars;
  }

  private void setBarsInternal(List<Bar> bars) {
    this.bars = bars;
  }

  @Transient
  public List<Bar> getBars() {
     // pseudo code
     if(check if bar is still lazy loaded) {
       // the field is still lazy loaded
       // attach the field to a session an initialize it
       // once the field is initialized, it can be returned
    }
    return getBarsInternal();       
  }

  public void setBars(List<Bar> bars) {
    setBarsInternal(bars);
  }
}

Ответ 3

Мы начали с одного сеанса за приложение, я перечислил мотивы и проблемы ниже,

Как следует из названия, все взаимодействие пользователя с приложением будет состоять из одной продолжительной живой сессии, и все (основное) сообщение с базой данных будет проходить через этот сеанс. Ваша "единица работы" теперь будет охватывать все взаимодействие с пользователем с приложением... что означает, что ваша "транзакция приложения" - это весь срок службы приложения.

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

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

  2. Воспользуйтесь кешем сеанса без необходимости загружать/выгружать объект каждый раз из БД.

Как:

Если вы выполняете всю основную работу в потоке графического интерфейса (почти все java-графические интерфейсы представляют собой потоки с одним событием, вы можете использовать локальный шаблон tweak thread, чтобы получить основную сессию из потока GUI.

Вопросы

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

  • Ваше приложение может пострадать от феномена "разбухающего сеанса"... с каждым вашим приложением все медленнее и медленнее, поскольку кеш сеанса становится все больше и больше, поскольку спящий режим должен проходить через все объекты в кеше сеанса для каждого запроса ( с огромным сеансом это может замедлить вас, если сеанс станет большим, при очистке его потребуется больше времени, даже если изменений нет). По существу преимущество № 3 выше может стать недостатком (из-за спячки транзакционной записи), если сеанс разрешен для роста. Обходной путь, который может быть полезным в некоторых случаях, может заключаться в использовании query.setReadOnly(true), session.setReadOnly() для запросов/сущностей, которые загружают объекты readonly в сеанс.
  • Ручное "выселение" для обеспечения согласованности сеанса. Возможно, вы используете SQL-запросы по разным причинам в разных местах приложения для выполнения обновлений/удалений в таком случае, когда вы делаете ручные выселения, чтобы предотвратить повреждение сеанса.
  • Транзакционная запись может возникнуть на вашем пути (в зависимости от того, как вы управляете длительным сеансом работы), если вы планируете получать доступ к данным из "других сеансов".
  • Пользователь может столкнуться с замораживанием GUI, когда спящий режим делает свою лазубую магию.
  • Классическая ленивая загрузка n + 1 запросов (см. ссылки ниже)
  • Проблемы с устаревшими данными. Если данные обновляются с помощью фонового задания или потока, не использующего основной сеанс. Ваш основной сеанс не будет обновляться с изменениями.

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

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

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

Ответ 4

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

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

Пока это звучит просто, программирование OO активно мешает. Мы скрываем операции модели глубоко в структуре вызовов, мы очень стараемся, чтобы пользователи кода не знали, что на самом деле делает код. В идеальном мире ваши средства разработки должны развернуть весь код, который требуется оператору, в один метод/функцию, обернуть его в транзакцию и сделать с ним.

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

Или у вас есть токен. Затем вы присоедините сеанс к токену и передадите его.

Но основной проблемой является и всегда было: Как присоединить сеанс к одной операции над моделью. Жесткие части должны знать, когда начинается работа, когда она заканчивается и как обрабатывать ошибки.

Для веб-приложений использование запроса является естественным способом определения операции: все, что происходит во время запроса, считается одним шагом. Приложение не сохраняет модель в памяти; все забывается в конце запроса и снова загружается из базы данных, когда приходит следующий запрос. Медленный, но управляемый.

Настольное приложение - совершенно другой вид зверя. Они обычно сохраняют всю модель в памяти все время. Мы не сохраняем изменений, если пользователь не попросит об этом (когда она "сохраняет" свою работу), потому что это будет слишком медленным, и поскольку нет ничего подобного запросу, нет простого, автоматического способа определить "операцию",.

Идея прикрепить операцию к событию хороша. Только в настольных приложениях вы можете иметь несколько потоков, которые обмениваются данными с событиями, и теперь вам нужен способ отметить все события как "они относятся к операции, запущенной с событием X, полученным давно". События обычно представляют собой небольшие, неизменные фрагменты данных, поэтому вы не можете присоединить к ним свою сессию. Но вам нужен какой-то токен, чтобы отметить все события, которые принадлежат друг другу. Именно поэтому большинство настольных приложений либо работают с сервером (который снова работает как веб-приложение), так и без большой модели в памяти или не используют базу данных, но сохраняют свою модель в пользовательском формате (думаю, Office).

Ответ 5

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

Сказав это, я согласен с вами в том, что таскать сеанс вокруг не имеет смысла, и вы должны освободить сеанс в конце каждой транзакции. Я бы привел к тому, чтобы иметь пул сеансов, из которых каждый поток может получить доступ к сеансу по мере необходимости, и отпустить его обратно в пул, когда это будет сделано. Вы можете использовать Spring, чтобы помочь вам управлять сеансами для этого.

Ответ 6

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

Ответ 7

Я не вижу действительно убедительной причины, по которой вы захотите по-другому в приложении с интерфейсом Swing, а затем с веб-интерфейсом. В последнем случае обычно создается сеанс для каждого запроса, хотя во многих случаях фактическое создание сеанса обрабатывается прозрачно (например, Spring).

Предполагая, что ваше приложение Swing реализует шаблон MVC, почему бы не просто создать сеанс в начале каждого метода контроллера и закрыть его в конце. Если ваше приложение многопоточно, вы можете использовать переменную ThreadLocal, чтобы легко охватить каждый сеанс определенным потоком. Это почти точно аналогично тому, что происходит в контейнере Servlet (т.е. В веб-приложении).

Ответ 8

Лучшая практика (ваш пробег может отличаться): Наиболее распространенным шаблоном в многопользовательском клиент-серверном приложении является сеанс за запрос. В этой модели запрос от клиента отправляется на сервер (где выполняется уровень сохранения Hibernate), открывается новый сеанс Hibernate, и все операции с базой данных выполняются в этой части работы. После завершения работы (и ответ для клиента был подготовлен) сессия очищается и закрывается. Вы также должны использовать транзакцию единой базы данных для обслуживания запроса клиентов, начиная и фиксируя ее при открытии и закрытии сеанса. Отношения между ними взаимно однозначны, и эта модель идеально подходит для многих приложений.

см.: http://docs.jboss.org/hibernate/stable/core/reference/en/html_single/#transactions-basics-uow

Ответ 9

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

Ответ 10

Если вы создаете threadpool, тогда вы не платите большую часть цены за соединение за транзакцию или за запрос, в зависимости от того, что вы делаете.

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

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

Вы можете посмотреть некоторые лучшие практики пользователей Spring, так как Hibernate, похоже, очень популярен в этой структуре.

Ответ 11

Поскольку создание сеанса в Oracle довольно дорого, и я в основном использую Oracle как RDBMS, я бы пошел на "один сеанс для всего приложения". "Один сеанс для обработки событий" кажется смешным в контексте приложения GUI. Есть причины сделать это в Интернете, но эти причины обычно не применяются для программы GUI.

Ответ 12

То, что вы действительно хотите избежать, - это сценарий сеанса за операцию, где для каждой отдельной операции базы данных создается и закрывается новый сеанс Hibernate.

РЕДАКТИРОВАТЬ: Игнорируйте мою раннюю бессмыслицу об одном сеансе на всю жизнь приложения, это вполне возможно (и, вероятно, самый эффективный способ) сделать это. Просто убедитесь, что вы правильно демаркируете свои транзакции - декларативно используйте что-то вроде Spring @Transaction или программно.

Ответ 13

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

Мои приложения являются гибридами Swing и JavaFX, и именно так я их исправил:

Мой первый подход заключался в том, чтобы не использовать Lazy Loading, но это оказалось кошмаром для некоторых объектов с более сложными графиками объектов.

Элемент управления таблицей из JavaFX играет важную роль в моих приложениях, я думаю, что свинг JTable сосет по сравнению с TableView на JavaFX.

Теперь, когда я загружаю JFrame или JInternalFrame или JDialog, я предварительно загружаю последние "обработанные" сущности, все их дети загружаются с помощью Lazy Loading, нет производительности, я привязываю данные к элементу управления TableView с видимыми первичными загружаемыми полями. Пользователь должен выбрать запись из таблицы TableView Grid для работы, когда они делают это Я получаю экземпляр объекта заново, так как у меня есть дескриптор его идентификатора, после того, как объект извлекается, тогда метод .size() вызывается для всех дочерних коллекций которые лениво загружаются. Это заставляет Hibernate загружать ленивые поля/свойства.

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

Я думаю, что этот подход повышает производительность в два раза. При извлечении данных списка быстро. Когда вы извлекаете объект с идентификатором, его точную точку, а так как дети все ленивы загружены означает, что выборки также легкие и компактные.