Hibernate + Swing - PullRequest
       32

Hibernate + Swing

12 голосов
/ 18 мая 2009

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

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

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

Итак, в чем же дело?

Ответы [ 13 ]

6 голосов
/ 18 мая 2009

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

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

6 голосов
/ 25 мая 2009

Я использовал Hibernate в многофункциональном интернет-приложении корпоративного размера, которое по своей технике напоминает комбинацию hibernate + swing. Я потратил довольно много времени на изучение различных шаблонов проектирования для использования гибернации в 3-уровневом приложении.

Hibernate не совсем предназначен для такого рода приложений, поэтому не было так много информации по этому вопросу. Я пробовал разные шаблоны проектирования, но большинство из них приводило к утечке памяти, проблемам с присоединением и откреплением объектов в сеансах (сеансах), проблемам с транзакциями и т. Д. И т. Д. Окончательное решение заключалось в использовании шаблона сеанс на запрос. По сути, при каждом запросе от логики пользовательского интерфейса к бизнес-логике вы создаете новый сеанс гибернации. Затем ваша бизнес-логика выполняет все, что вы хотите, и непосредственно перед завершением выполнения бизнес-логики вы очищаете и закрываете открытый сеанс. Таким образом, утечки памяти не будет, объекты будут правильно подключены и удалены из сеансов.

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

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

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


@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);
  }
}
5 голосов
/ 26 мая 2009

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

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

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

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

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

Как :

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

Вопросы

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

  1. Ваше приложение может страдать от явления «раздутого кэша сеанса»… с каждым вашим приложением становится все медленнее и медленнее по мере того, как кэш сеанса становится все больше и больше, поскольку hibernate должен проходить все объекты в кэше сеанса для каждого запроса ( с огромным сеансом это может замедлить вас. Если сеанс становится большим, очистка займет все больше и больше времени, даже если изменений нет). По существу, преимущество № 3, приведенное выше, может стать недостатком (из-за транзакционной обратной записи в спящем режиме), если разрешено увеличение размера сеанса. Обходной путь, который может быть полезен в некоторых случаях, может заключаться в использовании query.setReadOnly (true), session.setReadOnly () для запросов / сущностей, которые загружают объекты только для чтения в сеанс.
  2. Ручное «выселение» для поддержания согласованности сеанса. Возможно, вам пришлось использовать sql-запросы по разным причинам в разных местах приложения для выполнения обновлений / удалений, в случае, если вы выполняете ручное выселение, чтобы предотвратить повреждение сеанса.
  3. Транзакционная перезапись может возникнуть у вас (в зависимости от того, как вы управляете длительным сеансом), если вы планируете получить доступ к данным из «других сеансов».
  4. Пользователь может зависнуть в графическом интерфейсе, когда hibernate делает свою магию ленивой загрузки.
  5. Классическая ленивая загрузка n + 1 проблем с запросами (см. Ссылки ниже)
  6. Устаревшие проблемы с данными. Если данные обновляются каким-либо фоновым заданием или потоком, не использующим основной сеанс .. Ваш основной сеанс не будет обновлен с учетом изменений.

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

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

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

2 голосов
/ 25 мая 2009

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

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

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

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

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

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

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

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

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

1 голос
/ 21 мая 2009

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

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

1 голос
/ 18 мая 2009

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

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

1 голос
/ 18 мая 2009

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

1 голос
/ 18 мая 2009

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

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

0 голосов
/ 10 февраля 2013

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

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

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

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

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

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

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

0 голосов
/ 25 мая 2009

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

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

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