Wzorzec DAO w Javie

1. Przegląd

Wzorzec Data Access Object (DAO) jest wzorcem strukturalnym, który pozwala nam odizolować warstwę aplikacji / biznesową od warstwy trwałości (zwykle relacyjnej bazy danych, ale może to być dowolny inny mechanizm trwałości) przy użyciu abstrakcyjnego interfejsu API .

Funkcjonalność tego interfejsu API polega na ukryciu przed aplikacją wszystkich zawiłości związanych z wykonywaniem operacji CRUD w podstawowym mechanizmie magazynowania. Dzięki temu obie warstwy mogą ewoluować osobno, nie wiedząc nic o sobie.

W tym samouczku zagłębimy się w implementację wzorca i nauczymy się, jak go używać do tworzenia abstrakcji wywołań menedżera encji JPA.

2. Prosta implementacja

Aby zrozumieć, jak działa wzorzec DAO, stwórzmy podstawowy przykład.

Powiedzmy, że chcemy stworzyć aplikację zarządzającą użytkownikami. Aby model domeny aplikacji był całkowicie niezależny od bazy danych, utworzymy prostą klasę DAO, która zadba o to, aby te komponenty były od siebie równo oddzielone .

2.1. Klasa domeny

Ponieważ nasza aplikacja będzie działać z użytkownikami, musimy zdefiniować tylko jedną klasę do implementacji jej modelu domeny:

public class User { private String name; private String email; // constructors / standard setters / getters }

Użytkownik klasa jest tylko zwykły kontener dla danych użytkownika, więc nie realizuje żadnej innej wartości zachowań podkreślając.

Oczywiście najważniejszym wyborem projektu, którego musimy tutaj dokonać, jest to, jak utrzymać aplikację korzystającą z tej klasy w izolacji od jakiegokolwiek mechanizmu trwałości, który mógłby zostać zaimplementowany w pewnym momencie.

Cóż, to jest dokładnie problem, który próbuje rozwiązać wzorzec DAO.

2.2. API DAO

Zdefiniujmy podstawową warstwę DAO, abyśmy mogli zobaczyć, jak może ona całkowicie oddzielić model domeny od warstwy trwałości.

Oto DAO API:

public interface Dao { Optional get(long id); List getAll(); void save(T t); void update(T t, String[] params); void delete(T t); }

Z widokiem z lotu ptaka, to jasne, aby zobaczyć, że Dao interfejs API definiuje abstrakcyjne że operacje wykonuje CRUD na obiektach typu T .

Ze względu na wysoki poziom abstrakcji, który zapewnia interfejs, łatwo jest stworzyć konkretną, drobnoziarnistą implementację, która działa z obiektami użytkownika .

2.3. UserDao Class

Zdefiniujmy implementację interfejsu Dao dla użytkownika:

public class UserDao implements Dao { private List users = new ArrayList(); public UserDao() { users.add(new User("John", "[email protected]")); users.add(new User("Susan", "[email protected]")); } @Override public Optional get(long id) { return Optional.ofNullable(users.get((int) id)); } @Override public List getAll() { return users; } @Override public void save(User user) { users.add(user); } @Override public void update(User user, String[] params) { user.setName(Objects.requireNonNull( params[0], "Name cannot be null")); user.setEmail(Objects.requireNonNull( params[1], "Email cannot be null")); users.add(user); } @Override public void delete(User user) { users.remove(user); } }

W UserDao klasa realizuje wszystkie funkcje wymagane do pobierania, aktualizacji i usuwania użytkowników obiektów.

Dla uproszczenia lista użytkowników działa jak baza danych w pamięci, która jest zapełniana kilkoma obiektami użytkownika w konstruktorze .

Oczywiście łatwo jest refaktoryzować inne metody, aby mogły np. Współpracować z relacyjną bazą danych.

Chociaż w ramach tej samej aplikacji zarówno klasy User, jak i User excluded współistnieją niezależnie, nadal musimy sprawdzić, jak można wykorzystać tę drugą do ukrycia warstwy trwałości przed logiką aplikacji:

public class UserApplication { private static Dao userDao; public static void main(String[] args) { userDao = new UserDao(); User user1 = getUser(0); System.out.println(user1); userDao.update(user1, new String[]{"Jake", "[email protected]"}); User user2 = getUser(1); userDao.delete(user2); userDao.save(new User("Julie", "[email protected]")); userDao.getAll().forEach(user -> System.out.println(user.getName())); } private static User getUser(long id) { Optional user = userDao.get(id); return user.orElseGet( () -> new User("non-existing user", "no-email")); } }

Przykład jest wymyślony, ale pokazuje, w skrócie, motywacje stojące za wzorcem DAO. W tym przypadku główna metoda po prostu używa instancji User√ do wykonywania operacji CRUD na kilku obiektach User .

Najistotniejszym aspektem tego procesu jest to, w jaki sposób użytkownik Ticket ukrywa przed aplikacją wszystkie niskopoziomowe szczegóły dotyczące utrwalania, aktualizowania i usuwania obiektów .

3. Używanie wzorca z JPA

Wśród programistów istnieje ogólna tendencja do myślenia, że ​​wydanie JPA obniżyło funkcjonalność wzorca DAO do zera, ponieważ wzorzec staje się tylko kolejną warstwą abstrakcji i złożoności zaimplementowaną ponad warstwą dostarczoną przez menedżera jednostki JPA.

Bez wątpienia w niektórych scenariuszach jest to prawda. Mimo to czasami chcemy po prostu ujawnić naszej aplikacji tylko kilka specyficznych dla domeny metod interfejsu API menedżera encji. W takich przypadkach wzorzec DAO ma swoje miejsce.

3.1. JpaUserDao Class

Mając to na uwadze, stwórzmy nową implementację interfejsu Dao , abyśmy mogli zobaczyć, jak może on zawierać funkcjonalność, którą menedżer encji JPA zapewnia po wyjęciu z pudełka:

public class JpaUserDao implements Dao { private EntityManager entityManager; // standard constructors @Override public Optional get(long id) { return Optional.ofNullable(entityManager.find(User.class, id)); } @Override public List getAll() { Query query = entityManager.createQuery("SELECT e FROM User e"); return query.getResultList(); } @Override public void save(User user) { executeInsideTransaction(entityManager -> entityManager.persist(user)); } @Override public void update(User user, String[] params) { user.setName(Objects.requireNonNull(params[0], "Name cannot be null")); user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null")); executeInsideTransaction(entityManager -> entityManager.merge(user)); } @Override public void delete(User user) { executeInsideTransaction(entityManager -> entityManager.remove(user)); } private void executeInsideTransaction(Consumer action) { EntityTransaction tx = entityManager.getTransaction(); try { tx.begin(); action.accept(entityManager); tx.commit(); } catch (RuntimeException e) { tx.rollback(); throw e; } } }

JpaUserDao klasa jest w stanie pracować z dowolną relacyjną bazą danych obsługiwaną przez wdrożenie WZP.

Ponadto, jeśli przyjrzymy się bliżej klasie, zdamy sobie sprawę, w jaki sposób użycie funkcji Composition and Dependency Injection pozwala nam wywoływać tylko metody menedżera encji wymagane przez naszą aplikację.

Mówiąc najprościej, mamy dostosowane API dla domeny, a nie całe API menedżera encji.

3.2. Refaktoryzacja klasy użytkownika

W tym przypadku użyjemy Hibernate jako domyślnej implementacji JPA, dlatego odpowiednio zmienimy klasę User :

@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; private String email; // standard constructors / setters / getters }

3.3. Programowe uruchamianie programu JPA Entity Manager

Zakładając, że mamy już działającą instancję MySQL działającą lokalnie lub zdalnie, a tabelę bazy danych „users” wypełnioną niektórymi rekordami użytkowników, potrzebujemy menedżera encji JPA, abyśmy mogli użyć klasy JpaUser łódź do wykonywania operacji CRUD w Baza danych.

W większości przypadków osiągamy to za pomocą typowego pliku „persistence.xml” , który jest standardowym podejściem.

W tym przypadku podejmiemy podejście „bez xml” i uzyskamy menedżera encji ze zwykłą Javą za pomocą przydatnej klasy EntityManagerFactoryBuilderImpl firmy Hibernate .

Aby uzyskać szczegółowe wyjaśnienie, jak załadować implementację JPA za pomocą języka Java, zapoznaj się z tym artykułem.

3.4. UserApplication Class

Finally, let's refactor the initial UserApplication class, so it can work with a JpaUserDao instance and execute CRUD operations on the User entities:

public class UserApplication { private static Dao jpaUserDao; // standard constructors public static void main(String[] args) { User user1 = getUser(1); System.out.println(user1); updateUser(user1, new String[]{"Jake", "[email protected]"}); saveUser(new User("Monica", "[email protected]")); deleteUser(getUser(2)); getAllUsers().forEach(user -> System.out.println(user.getName())); } public static User getUser(long id) { Optional user = jpaUserDao.get(id); return user.orElseGet( () -> new User("non-existing user", "no-email")); } public static List getAllUsers() { return jpaUserDao.getAll(); } public static void updateUser(User user, String[] params) { jpaUserDao.update(user, params); } public static void saveUser(User user) { jpaUserDao.save(user); } public static void deleteUser(User user) { jpaUserDao.delete(user); } }

Even when the example is pretty limited indeed, it remains useful for demonstrating how to integrate the DAO pattern's functionality with the one that the entity manager provides.

In most applications, there's a DI framework, which is responsible for injecting a JpaUserDao instance into the UserApplication class. For simplicity's sake, we've omitted the details of this process.

Najbardziej istotnym punktem na stres oto jak JpaUserDao klasa pomaga utrzymać UserApplication klasę całkowicie agnostycznego o tym, jak warstwy trwałości operacji wykonuje CRUD .

Ponadto mogliśmy zamienić MySQL na dowolny inny RDBMS (a nawet na płaską bazę danych) dalej w przyszłości, a mimo to nasza aplikacja będzie nadal działać zgodnie z oczekiwaniami, dzięki poziomowi abstrakcji zapewnianemu przez interfejs Dao i menedżera encji .

4. Wniosek

W tym artykule przyjrzeliśmy się dogłębnie kluczowym koncepcjom wzorca DAO, sposobowi implementacji go w Javie i używaniu go w połączeniu z menadżerem encji JPA.

Jak zwykle wszystkie przykłady kodu przedstawione w tym artykule są dostępne w serwisie GitHub.