1. Översikt
Data Access Object (DAO) -mönstret är ett strukturellt mönster som gör att vi kan isolera applikations- / affärsskiktet från persistenslagret (vanligtvis en relationsdatabas, men det kan vara vilken annan persistensmekanism som helst) med ett abstrakt API .
Funktionaliteten i detta API är att dölja från applikationen alla komplexiteter som är involverade i att utföra CRUD-operationer i den underliggande lagringsmekanismen. Detta gör att båda skikten kan utvecklas separat utan att veta något om varandra.
I den här handledningen tar vi ett djupdyk i mönstrets implementering och vi lär oss hur man använder det för att abstrahera samtal till en JPA-enhetschef.
2. En enkel implementering
För att förstå hur DAO-mönstret fungerar, låt oss skapa ett grundläggande exempel.
Låt oss säga att vi vill utveckla en applikation som hanterar användare. För att hålla applikationens domänmodell helt agnostisk för databasen skapar vi en enkel DAO-klass som tar hand om att hålla dessa komponenter snyggt frikopplade från varandra .
2.1. Domänklassen
Eftersom vår applikation kommer att fungera med användare behöver vi bara definiera en klass för att implementera domänmodellen:
public class User { private String name; private String email; // constructors / standard setters / getters }
Den användar klassen är bara en vanlig behållare för användardata, så att den inte genomför någon annan beteende värt att betona.
Naturligtvis är det mest relevanta designvalet som vi behöver göra här hur man håller applikationen som använder den här klassen isolerad från alla uthållighetsmekanismer som kan implementeras någon gång.
Tja, det är precis den fråga som DAO-mönstret försöker ta itu med.
2.2. DAO API
Låt oss definiera ett grundläggande DAO-lager så att vi kan se hur det kan hålla domänmodellen helt frikopplad från uthållighetsskiktet.
Här är 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); }
Ur ett fågelperspektiv, är det tydligt att se att Dao -gränssnittet definierar ett abstrakt API som utför CRUD operationer på objekt av typen T .
På grund av den höga abstraktionsnivå att gränssnittet ger, är det lätt att skapa en konkret, finkornig implementering som arbetar med användarobjekt.
2.3. Den UserDao Class
Låt oss definiera en användarspecifik implementering av Dao- gränssnittet:
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); } }
De UserDao klass implementerar alla funktioner som krävs för att hämta, uppdatera och ta bort användarobjekt.
För enkelhetens skull, de användare listan fungerar som en in-memory databas som fylls med ett par användarobjekt i konstruktören .
Naturligtvis är det enkelt att refaktorera de andra metoderna så att de kan arbeta till exempel med en relationsdatabas.
Medan både användare och UserDao klasser samexistera självständigt inom samma program, vi måste fortfarande se hur det senare kan användas för att hålla uthållighet lager dold programlogik:
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")); } }
Exemplet är konstruerat, men det visar i ett nötskal motivationen bakom DAO-mönstret. I detta fall huvud metoden bara använder en UserDao exempel för att utföra CRUD operationer på några användarobjekt.
Den mest relevanta aspekten av denna process är hur UserDao döljer från applikationen alla detaljer på låg nivå om hur objekten kvarstår, uppdateras och raderas .
3. Använda mönstret med JPA
Det finns en allmän tendens hos utvecklare att tro att frisläppandet av JPA nedgraderades till noll DAO-mönsterets funktionalitet, eftersom mönstret blir bara ett lager av abstraktion och komplexitet som implementeras ovanpå det som tillhandahålls av JPA: s enhetschef.
Utan tvekan, i vissa scenarier är detta sant. Ändå vill vi ibland bara exponera några få domänspecifika metoder för enhetshanterarens API för vår applikation. I sådana fall har DAO-mönstret sin plats.
3.1. Den JpaUserDao Class
Med det sagt, låt oss skapa en ny implementering av Dao- gränssnittet, så att vi kan se hur det kan inkapsla funktionaliteten som JPA: s enhetshanterare ger ur lådan:
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; } } }
Den JpaUserDao klassen är i stånd att arbeta med någon relationsdatabas med stöd av genomförande JPA.
Dessutom, om vi tittar noga på klassen, kommer vi att inse hur användningen av komposition och beroendeinjektion tillåter oss att bara ringa de enhetshanteringsmetoder som krävs av vår applikation.
Enkelt uttryckt har vi ett domänspecifikt skräddarsytt API snarare än hela enhetshanterarens API.
3.2. Refactoring av användar Class
I det här fallet använder vi Hibernate som genomförande JPA standard, vilket vi Refactor den Användar klassen enligt följande:
@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. Starta upp en JPA Entity Manager programmatiskt
Om vi antar att vi redan har en fungerande instans av MySQL som körs antingen lokalt eller fjärrstyrt och en databas-tabell "användare" fyllda med vissa användarposter, måste vi skaffa en JPA-enhetshanterare, så att vi kan använda JpaUserDao- klassen för att utföra CRUD-operationer i databas.
I de flesta fall uppnår vi detta via den typiska filen "persistence.xml" , som är standardmetoden.
I det här fallet tar vi ett “xml-mindre” tillvägagångssätt och får entitetshanteraren med vanlig Java genom Hibernates praktiska EntityManagerFactoryBuilderImpl- klass.
För en detaljerad förklaring om hur du startar en JPA-implementering med Java, se den här artikeln.
3.4. Den 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.
Den mest relevanta punkten att betona här är hur den JpaUserDao klassen hjälper till att hålla userapplication klassen helt agnostiker om hur den uthållighet lager utför CRUD-operationer .
Dessutom kan vi byta MySQL mot alla andra RDBMS (och till och med för en platt databas) längre fram på vägen, och ändå fortsätter vår applikation att fungera som förväntat tack vare den abstraktionsnivå som tillhandahålls av Dao- gränssnittet och enhetshanteraren .
4. Slutsats
I den här artikeln tog vi en djupgående titt på DAO-mönstrets nyckelbegrepp, hur man implementerar det i Java och hur man använder det ovanpå JPA: s enhetshanterare.
Som vanligt är alla kodprover som visas i den här artikeln tillgängliga på GitHub.