Страницы

Поиск по вопросам

суббота, 4 января 2020 г.

Сессии и DAO классы в Hibernate

#java #база_данных #hibernate #orm


Здравствуйте.

Есть маленький проектик работающий с БД. В качестве ORM решения выбран Hibernate 4. 

Попутно обуздывая фреймворк, создал классы сущностей, связал аннотациями, использовал
DAO pattern. Но: в имплементации ДАО классов в каждом изменяющем БД методе сессия открывалась
и закрывалась прямо в методе (как и в этой туториал ссылочке http://javaxblog.ru/article/java-hibernate-1/).
Пример

Session session = null;
        try {
            session = HibernateUtil.getSessionFactory().openSession();
            session.beginTransaction();
            // Здесь добавляю, обновляю или удаляю
            // ...
            session.getTransaction().commit();
        } catch (Exception e) {
            // Обрабатываю
            // rollback()
        } finally {
            // Закрываю
            if (session != null && session.isOpen()) {
                session.close();
            }
        }


Но что-то подсказывает, что это как-то, мягко говоря, не правильно. 
Порыв немного интернет, наткнулся на статейку (https://developer.jboss.org/wiki/GenericDataAccessObjects),
где говорится, что ДАО имплементации не должны создавать сессию внутри, а на неё нужно
лишь ссылаться. Если я правильно понял, ДАО имплементаций не должно волновать создание
сессий. 


  You could also use constructor injection. How you set the Session and what scope
this Session has is of no concern to the actual DAO implementation. A DAO should not
control transactions or the Session scope.


Интересует вопрос опытных разработчиков: 
создавать ли сессию один раз на всё приложение и просто в сеттере давать ссылочку
ДАО классам (при этом сессию не закрывать), или же в каждом методе ДАО класса создавать
и закрывать сессию? 
    


Ответы

Ответ 1



Жаль, что автор не нашёл время сам ответить на свой вопрос. Хоть я ни разу не опытный разработчик, попробую ответить на основании информации, которую нашёл в сети на текущий день. На многих ресурсах рекомендуют использовать Hibernate в связке со Spring. Мне не нравится этот совет тем, что вводится лишняя "сущность". Человек учится пользоваться отвёрткой, а ему хотят всучить шуруповёрт. Да, вроде бы шуруповёрт круче, но и отвёрткой уметь пользоваться тоже не будет лишним. Ручное открытие/закрытие сессии. В первую очередь напрашивается такая структура взаимодействия: слой DAO содержит только основные (CRUD - create, read, update, delete) операции взаимодействия с базой данных; в бизнес-слой выносятся операции, которые можно (и нужно) объединять в транзакции. Выглядит DAO-класс примерно так: public class ItemDAOImpl implements ItemDAO { private Session session; public ItemDAOImpl(Session session) { this.session = session; } @Override public Item get(long id) { return session.get(Item.class, id, LockMode.PESSIMISTIC_READ); } @Override public long create(Item item) { return (Long) session.save(item); } @Override public void update(Item item) { session.update(item); } @Override public Item delete(long id) { Item item = session.byId(Item.class).load(id); session.delete(item); return item; } } Сессия session создаётся в бизнес-слое и передаётся в конструктор DAO: public class AdminServiceImpl extends PersonServiceImpl implements AdminService { SessionFactory sessionFactory; public AdminServiceImpl(SessionFactory sessionFactory){ this.sessionFactory = sessionFactory; } @Override public long createItem(Item item) throws DBException { try (Session session = sessionFactory.openSession()){ Transaction transaction = session.beginTransaction(); ItemDAO dao = new ItemDAOImpl(session); long id = dao.create(item); transaction.commit(); return id; } catch (HibernateException | NoResultException e) { throw new DBException(e); } } @Override public void updateItem(Item item) throws DBException { try (Session session = sessionFactory.openSession()){ Transaction transaction = session.beginTransaction(); ItemDAO dao = new ItemDAOImpl(session); dao.update(item); transaction.commit(); } catch (HibernateException | NoResultException e) { throw new DBException(e); } } @Override public void deleteItem(long id) throws DBException { try (Session session = sessionFactory.openSession()){ Transaction transaction = session.beginTransaction(); ItemDAO dao = new ItemDAOImpl(session); Item item = dao.delete(id); transaction.commit(); } catch (HibernateException | NoResultException | IllegalArgumentException e) { throw new DBException(e); } } } Недостаток метода очевиден - при большой нагрузке должен активно работать сборщик мусора, чтобы освобождать память от DAO- и сервисных объектов. Это осталось за кадром, но подразумевается, что также должен создаваться объект сервисного класса AdminServiceImpl. В конструкторе этот объект получает ссылку на объект типа SessionFactory. Стратегия открытия сессии Чтобы избавиться от постоянного создания dao - и сервисных объектов, нужен механизм создания сессии или получения текущей сессии в каждом из этих объектов независимо друг от друга. И тут на помощь приходят стратегии открытия-закрытия сессии. Рекомендую эту статью к прочтению. Допустим, что выбираем стратегию ManagedSessionContext - каждому потоку по отдельной сессии. Тогда для получения сессии можно будет использовать Session session = sessionFactory.getCurrentSession(); Увы, сразу это не заработает. Чтобы заработало, нужно в hibernate.cfg.xml прописать thread. Или второй способ - в конфигурацию hibernate добавить свойство configuration.setProperty("hibernate.current_session_context_class", "thread"); Уже знакомый DAO-класс будет теперь выглядеть так: public class ItemDAOImpl implements ItemDAO { ItemDAOImpl() { } @Override public Item get(long id) { return DBService.getSessionFactory() .getCurrentSession() .get(Item.class, id, LockMode.PESSIMISTIC_READ); } @Override public long create(Item item) { return (Long) DBService.getSessionFactory() .getCurrentSession() .save(item); } @Override public void update(Item item) { DBService.getSessionFactory().getCurrentSession() .update(item); } @Override public Item delete(long id) { Session session = DBService.getSessionFactory().getCurrentSession(); Item item = session.byId(Item.class).load(id); session.delete(item); return item; } } Здесь DBService - абстрактный класс, содержащий ссылку на объект SessionFactory. Так выглядит функция getSessionFactory(): public static SessionFactory getSessionFactory(){ return sessionFactory; } А вот и переделанный сервисный класс: public class AdminServiceImpl extends PersonServiceImpl implements AdminService { AdminServiceImpl() { } @Override public long createItem(Item item) throws DBException { Transaction transaction = DBService.getTransaction(); try { ItemDAO dao = DaoFactory.getItemDAO(); long id = dao.create(item); transaction.commit(); return id; } catch (HibernateException | NoResultException e) { DBService.transactionRollback(transaction); throw new DBException(e); } } @Override public void updateItem(Item item) throws DBException { Transaction transaction = DBService.getTransaction(); try { ItemDAO dao = DaoFactory.getItemDAO(); dao.update(item); transaction.commit(); } catch (HibernateException | NoResultException e) { DBService.transactionRollback(transaction); throw new DBException(e); } } @Override public void deleteItem(long id) throws DBException { Transaction transaction = DBService.getTransaction(); try { ItemDAO dao = DaoFactory.getItemDAO(); Item item = dao.delete(id); transaction.commit(); } catch (HibernateException | NoResultException | IllegalArgumentException | IllegalStateException e) { DBService.transactionRollback(transaction); throw new DBException(e); } } } Вот используемые функции из DBService: public static Transaction getTransaction(){ Session session = DBService.getSessionFactory().getCurrentSession(); Transaction transaction = DBService.getSessionFactory().getCurrentSession().getTransaction(); if (!transaction.isActive()) { transaction = session.beginTransaction(); } return transaction; } public static void transactionRollback(Transaction transaction){ if (transaction.getStatus() == TransactionStatus.ACTIVE || transaction.getStatus() == TransactionStatus.MARKED_ROLLBACK) { transaction.rollback(); } } А вот класс DaoFactory для получения инстанса ItemDAO: public abstract class DaoFactory { private static ItemDAO itemDAO = new ItemDAOImpl(); public static ItemDAO getItemDAO() { return itemDAO; } } Собственно, на этом, думаю, всё. Для тех, кто хочет пощупать код, вот ссылки на github: - ручное управление сессией; - управление при помощи ManagedSessionContext.

Ответ 2



когда слишком много операций на вставку или обновление необходимо делать, то кадрый раз открытие и закрытие сесии отнимет много времени в совокупности. По этой причине данные действия можно позволить решить выше. private Session currentSession; private Transaction currentTransaction; public Session openCurrentSession() { currentSession = HibernateUtil.HibernateUtil.getSessionFactory().openSession(); return currentSession; } public Session openCurrentSessionwithTransaction() { currentSession = HibernateUtil.getSessionFactory().openSession(); currentTransaction = currentSession.beginTransaction(); return currentSession; } public void closeCurrentSession() { currentSession.close(); } public void closeCurrentSessionwithTransaction() { currentTransaction.commit(); currentSession.close(); } public void update(Object o) { this.currentSession.update(o); } таким образом, Вы сами решаете, когда открывать сессию и когда закрывать.

Ответ 3



Использование ссылки на SessionFactory внутри реализации DAO и создание сессии на каждый вызов дао методов. Для массивных изменений рекомендуется batch update.

Комментариев нет:

Отправить комментарий