Wprowadzenie do DAO
Podczas pracy z bazą danych przez JDBC czy nawet przez Hibernate kod często okazuje się bardziej uciążliwy niż byśmy tego chcieli. Zapytanie do bazy danych często zawiera:
- walidacji danych
- ustawienie parametrów żądania
- Wybór zapytania HQL w zależności od parametrów zapytania
- konstruowanie zapytania przy użyciu API kryteriów
- ustawienia buforowania
- wstępna obsługa błędów itp.
Dlatego powszechną praktyką jest tworzenie specjalnych klas do pracy z bazą danych. Takie klasy nazywane są DAO, Data Access Object. Ich zadaniem jest ukrycie wszystkich zawiłości pracy z bazą danych oraz zapewnienie pięknego i wygodnego interfejsu na zewnątrz.
Przykład:
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.setParameterr(“name”, name);
return query.getSingleResult();
}
}
Mamy klasę EmployeeDAO , za pomocą której uzyskujemy obiekty typu Employee z bazy danych. Sama klasa, choć nafaszerowana adnotacjami, nie zawiera metod pozwalających zapisać się do bazy danych.
Korzyści z DAO
Takie podejście ma wiele zalet:
Po pierwsze całkowicie ukryliśmy pracę z bazą danych w klasie DAO. Jeśli zdecydujesz się w przyszłości przepisać wszystkie zapytania z HQL na Criteria API lub Native Query, nie wpłynie to w żaden sposób na kod poza tą klasą.
Po drugie, możesz skomplikować zachowanie tych metod. Możesz dodać buforowanie, zdarzenia, walidację parametrów. To wszystko będzie ukryte przed zewnętrznym kodem.
Po trzecie, jeśli potrzebujesz metody, która jeszcze nie istnieje, po prostu dodaj ją tutaj. Na przykład potrzebuję metody, która zwróci wszystkie zadania użytkownika, które już wygasły. Potem zrobię tylko to:
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();
}
}
Dodałem dwie metody do klasy:
- getExpiredTasksCount() - zwraca liczbę wygasłych zadań dla użytkownika
- getExpiredTasks() - zwraca listę wygasłych zadań dla użytkownika
Potrzebuję metod - dodałem je. I od razu mogę z niego skorzystać. Zoptymalizuję je później.
Co więcej, metody te można objąć testami jednostkowymi przed przepisaniem i optymalizacją, dzięki czemu będziemy wiedzieć, że praca z bazą danych pozostała taka sama jak była.
Standardowe podejście
Bardzo często klasy DAO mają takie same metody. Na przykład te:
T getById (końcowy długi identyfikator) | Uzyskaj obiekt według jego identyfikatora |
List<T> getItems (int from, int count) | Pobierz listę obiektów w zadanym zakresie |
List<T> pobierz wszystko () | Pobierz wszystkie obiekty danego typu |
int getCount () | Znajdź liczbę obiektów |
Zapisz T (ostateczna jednostka T) | Zapisz obiekt do bazy danych |
Aktualizacja T (końcowa jednostka T) | Zaktualizuj obiekt w bazie danych |
void delete (końcowa jednostka T) | Usuń obiekt z bazy danych |
void deleteById (końcowy długi identyfikator jednostki) | Usuń obiekt z bazy danych według identyfikatora |
Metody te można znaleźć w prawie każdej klasie DAO na świecie. Teraz, jeśli istnieje jakaś klasa DAO, to z 90% prawdopodobieństwem będzie miała takie metody.
Tak, mogą być inni, ale tacy też będą. Ponieważ jest to bardzo wygodne. I pozwala ci nie wchodzić w interakcje bezpośrednio z bazą lub Hibernacją.
A jeśli istnieją identyczne metody, to czego potrzebują? Zgadza się, umieść to w klasie bazowej.
Wygląda to mniej więcej tak:
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();
}
}
A wtedy nasz EmployeeDAO będzie wyglądał tak:
public class EmployeeDAO extends AbstractHibernateDAO<Employee> {
public EmployeeDAO (){
super(Employee.class );
}
}
A TaskDAO wygląda tak:
public class TaskDAO extends AbstractHibernateDAO<Task> {
public TaskDAO (){
super(Task.class );
}
}
I obie te klasy będą miały wszystkie metody, które zadeklarowaliśmy na AbstractHibernateDAO . Ujednolicenie jest bardzo wygodne i praktyczne.