1. Знайомство з DAO

Під час роботи з базою даних через JDBC або навіть через Hibernate код часто виходить більш громіздким, ніж хотілося б. Запит до бази часто містить:

  • валідацію даних
  • встановлення параметрів запиту
  • вибір HQL-запиту в залежності від параметрів запиту
  • конструювання запиту за допомогою Criteria API
  • налаштування кешування
  • первинну обробку помилок тощо.

Тому загальноприйнята практика — створювати спеціальні класи для роботи з базою даних. Такі класи отримали назву DAO, Data Access Object. Їхнє завдання — приховувати всі складнощі роботи з базою та надавати гарний та зручний інтерфейс.

Приклад:


public class EmployeeDAO {
 
   public List<Employee> getEmployeeList(int from, int count) {
   String hqlQuery = "від Employee";
   Query<Employee> query = session.createQuery(hqlQuery, Employee.class);
   query.setFirstResult(from);
   query.setMaxResults(count);
   return query.getResultList();
  }
 
public int getEmployeeCount() {
String hqlQuery = "select count(*) from Employee";
     Query<Integer> query = session.createQuery(hqlQuery, Integer.class);
     return query.getSingleResult();
   }
 
public Employee getEmployeeByUniqName(String name) {
String hqlQuery = "з Employee where name = :name";
     Query<Integer>query = session.createQuery(hqlQuery, Employee.class);
     query.setParameter(“name”, name);
     return query.getSingleResult();
   }
}

У нас є клас EmployeeDAO, за допомогою якого ми отримуємо з бази даних об'єкти типу Employee. Сам клас, хоч і напханий анотаціями, не містить методів збереження себе в базу.

2. Переваги DAO

Переваг такого підходу дуже багато:

По-перше, ми повністю приховали у DAO-класі роботу з базою даних. Якщо ти в майбутньому вирішиш переписати всі запити з HQL на Criteria API або Native Query, це жодним чином не зачепить код за межами цього класу.

По-друге, ти можеш ускладнювати поведінку цих методів. Можна додати кешування, events, валідацію параметрів. Це все буде приховано від коду зовні.

По-третє, якщо тобі потрібний метод, якого ще немає, ти просто додаєш його сюди. Наприклад, мені потрібен метод, який поверне всі завдання користувача, які вже були проекспайрені. Тоді я просто зроблю так:


public class EmployeeDAO {
 
   public List<Task> getExpiredTasks(int userId, int from, int count) {
   String hqlQuery = “з Task where task.user.id = :id and deadline < curdate()”;
   Query<Task> query = session.createQuery(hqlQuery, Task.class);
   query.setFirstResult(from);
   query.setMaxResults(count);
   return query.getResultList();
  }
 
   public int getExpiredTasksCount(int userId) {
   String hqlQuery = “select count(*) from Task where task.user.id = :id and deadline < curdate()”;
   Query<Integer> query = session.createQuery(hqlQuery, Integer.class);
   return query.getSingleResult();
  }
}

Я додав до класу два методи:

  • getExpiredTasksCount() — повертає кількість проекспайрених завдань у користувача
  • getExpiredTasks() — повертає список проекспайрених завдань у користувача

Мені потрібні методи — я їх додав. І одразу можу використати. А оптимізувати їх буду потім.

Більш того, ці методи можна покрити Unit-тестами перед переписуванням та оптимізаціями, таким чином ми знатимемо, що робота з базою даних залишилася такою, як і була.

3. Стандартний підхід

Дуже часто DAO-класи мають методи, які збігаються. Наприклад, такі:

T getById(final long id) Отримати об'єкт за його id
List<T> getItems(int from, int count) Отримати список об'єктів за вказаним діапазоном
List<T> getAll() Отримати всі об'єкти цього типу
int getCount() Дізнатися кількість об'єктів
T save(final T entity) Зберегти об'єкт до бази
T update(final T entity) Оновити об'єкт у базі
void delete(final T entity) Видалити об'єкт із бази
void deleteById(final long entityId) Видалити об'єкт із бази за id

Ці методи зустрічаються практично у кожного DAO-класу у світі. Ось якщо є якийсь DAO-клас, то з 90% ймовірністю він матиме такі методи.

Так, можуть бути й інші, але такі також будуть. Тому що це дуже зручно. І дозволяє не взаємодіяти з базою або Hibernate безпосередньо.

А якщо є однакові методи, то їх треба що? Правильно, винести до базового класу.

Виглядає він приблизно так:


public abstract class AbstractHibernateDao<T > {
    private final Class & clazz;
    private SessionFactory sessionFactory;
 
    public AbstractHibernateDao(final Class&T> clazzToSet) {
    this.clazz = clazzToSet;
    }
 
    public T getById(final long id) {
    return(T) getCurrentSession().get(clazz, id);
    }
 
    public List<T> getItems(int from, int count) {
    Query query = getCurrentSession().createQuery(clazz , "from" + clazz.getName())
    query.setFirstResult(offset);
    query.setMaxResults(count);
  return query.singleResult();
    }
 
    public List<T> findAll() {
    return getCurrentSession().createQuery(clazz, "from" + clazz.getName()).list();
    }
 
    public T create(final T entity) {
    getCurrentSession().saveOrUpdate(entity);
    return entity;
    }
 
    public T update(final T entity) {
    return (T) getCurrentSession().merge(entity);
    }
 
    public void delete(final T entity) {
    getCurrentSession().delete(entity);
    }
 
    public void deleteById(final long entityId) {
    final T entity = getById(entityId);
    delete (entity);
    }
 
    protected Session getCurrentSession() {
    return sessionFactory.getCurrentSession();
    }
}

І тоді наш EmployeeDAO виглядатиме так:


public class EmployeeDAO extends AbstractHibernateDAO<Employee> {
 
   public EmployeeDAO (){
  super(Employee.class );
   }
}

А TaskDAO — так:


public class TaskDAO extends AbstractHibernateDAO<Task> {
 
   public TaskDAO () {
  super(Task.class);
   }
}

І в обох цих класів будуть всі методи, які ми оголосили у AbstractHibernateDAO. Уніфікація — це дуже зручно та практично.