Знакомство с DAO

При работе с базой данных через JDBC или даже через Hibernate код часто получается более громоздким, чем это хотелось бы. Запрос к базе часто содержит:

  • валидацию данных
  • установку параметров запроса
  • выбор HQL-запроса в зависимости от параметров запроса
  • конструирование запроса с помощью Criteria API
  • настройку кэширования
  • первичную обработку ошибок и т. п.

Поэтому общепринятая практика — создавать специальные классы для работы с базой данных. Такие классы получили название DAO, Data Access Object. Их задача — скрывать все сложности работы с базой и предоставлять наружу красивый и удобный интерфейс.

Пример:


public class EmployeeDAO {
 
   public List<Employee> getEmployeeList(int from, int count) {
   	String hqlQuery = “from 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 = “from Employee where name = :name”;
     	Query<Integer> query = session.createQuery(hqlQuery, Employee.class);
     	query.setParametуr(“name”, name);
     	return query.getSingleResult();
   }
}

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

Преимущества DAO

Преимуществ у такого подхода очень много:

Во-первых, мы полностью скрыли в DAO-классе работу с базой данных. Если ты в будущем решишь переписать все запросы с HQL на Criteria API или на Native Query, это никак не затронет код за границами этого класса.

Во-вторых, ты можешь усложнять поведение этих методов. Можно добавить кеширование, events, валидацию параметров. Это все будет скрыто от кода снаружи.

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


public class EmployeeDAO {
 
   public List<Task> getExpiredTasks(int userId, int from, int count) {
   	String hqlQuery = “from 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-тестами перед переписыванием и оптимизациями, таким образом мы будем знать, что работа с базой данных осталась такой, как и была.

Стандартный подход

Очень часто у 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<T> 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. Унификация — это очень удобно и практично.