DAO-mønsteret i Java

1. Oversigt

Data Access Object (DAO) mønster er et strukturelt mønster, der giver os mulighed for det isoler applikations- / forretningslaget fra persistenslaget (normalt en relationsdatabase, men det kan være enhver anden persistensmekanisme) ved hjælp af en abstrakt API.

Funktionaliteten af ​​denne API er at skjule alle kompleksiteter, der er involveret i udførelse af CRUD-operationer i den underliggende lagermekanisme, fra applikationen. Dette gør det muligt for begge lag at udvikle sig separat uden at vide noget om hinanden.

I denne vejledning tager vi et dybt dybde i mønsterets implementering, og vi lærer at bruge det til at abstrahere opkald til en JPA-enhedsadministrator.

2. En simpel implementering

Lad os oprette et grundlæggende eksempel for at forstå, hvordan DAO-mønsteret fungerer.

Lad os sige, at vi vil udvikle en applikation, der administrerer brugere. For at holde applikationens domænemodel fuldstændig agnostisk omkring databasen opretter vi en simpel DAO-klasse, der tager sig af at holde disse komponenter pænt afkoblet fra hinanden.

2.1. Domæneklassen

Da vores applikation fungerer sammen med brugerne, skal vi kun definere en klasse til implementering af dens domænemodel:

offentlig klasse bruger {privat strengnavn; privat streng e-mail; // konstruktører / standard sættere / getters}

Det Bruger klasse er bare en almindelig container til brugerdata, så den implementerer ikke nogen anden adfærd, der er værd at understrege.

Naturligvis er det mest relevante designvalg, som vi skal tage her, hvordan man holder applikationen, der bruger denne klasse, isoleret fra enhver persistensmekanisme, der kunne implementeres på et eller andet tidspunkt.

Nå, det er netop det spørgsmål, som DAO-mønsteret forsøger at løse.

2.2. DAO API

Lad os definere et grundlæggende DAO-lag, så vi kan se, hvordan det kan holde domænemodellen helt afkoblet fra persistenslaget.

Her er DAO API:

offentlig grænseflade Dao {Valgfri get (lang id); Liste getAll (); ugyldig gemme (T t); ugyldig opdatering (T t, String [] params); ugyldig sletning (Tt); }

Fra et fugleperspektiv er det klart at se, at Dao interface definerer en abstrakt API, der udfører CRUD-operationer på objekter af typen T.

På grund af det høje abstraktionsniveau, som grænsefladen giver, er det let at oprette en konkret, finkornet implementering, der fungerer med Bruger genstande.

2.3. Det UserDao Klasse

Lad os definere en brugerspecifik implementering af Dao grænseflade:

offentlig klasse UserDao implementerer Dao {private List-brugere = ny ArrayList (); offentlig UserDao () {users.add (ny bruger ("John", "[email protected]")); users.add (ny bruger ("Susan", "[email protected]")); } @ Override public Valgfri get (lang id) {return Optional.ofNullable (users.get ((int) id)); } @ Override offentlig liste getAll () {returnere brugere; } @ Overstyr offentlig ugyldig gem (brugerbruger) {brugere.add (bruger); } @Override offentlig ugyldig opdatering (brugerbruger, streng [] params) {user.setName (Objects.requireNonNull (params [0], "Navn kan ikke være nul")); user.setEmail (Objects.requireNonNull (params [1], "E-mail kan ikke være nul")); users.add (bruger); } @ Overstyr offentlig tomrumslet (brugerbruger) {brugere. Fjern (bruger); }}

Det UserDao klasse implementerer al den nødvendige funktionalitet til at hente, opdatere og fjerne Bruger genstande.

For enkelhedens skyld er brugerliste fungerer som en in-memory database, der er befolket med et par Bruger objekter i konstruktøren.

Selvfølgelig er det let at refaktorere de andre metoder, så de f.eks. Kan arbejde med en relationsdatabase.

Mens begge de Bruger og UserDao klasser eksisterer uafhængigt inden for samme applikation, vi skal stadig se, hvordan sidstnævnte kan bruges til at holde persistenslaget skjult fra applikationslogik:

offentlig klasse UserApplication {privat statisk Dao userDao; public static void main (String [] args) {userDao = new UserDao (); Brugerbruger1 = getUser (0); System.out.println (bruger1); userDao.update (bruger1, ny streng [] {"Jake", "[e-mailbeskyttet]"}); Brugerbruger2 = getUser (1); userDao.delete (bruger2); userDao.save (ny bruger ("Julie", "[e-mailbeskyttet]")); userDao.getAll (). forEach (bruger -> System.out.println (user.getName ())); } privat statisk bruger getUser (lang id) {Valgfri bruger = userDao.get (id); returner user.orElseGet (() -> ny bruger ("ikke-eksisterende bruger", "ingen e-mail")); }}

Eksemplet er konstrueret, men det viser i en nøddeskal motivationen bag DAO-mønsteret. I dette tilfælde er vigtigste metode bruger bare en UserDao eksempel for at udføre CRUD-operationer på nogle få Bruger genstande.

Den mest relevante facet i denne proces er hvordan UserDao skjuler fra applikationen alle detaljer på lavt niveau om, hvordan objekterne vedholdes, opdateres og slettes.

3. Brug af mønsteret med JPA

Der er en generel tendens blandt udviklere til at tro, at frigivelsen af ​​JPA nedgraderes til nul for DAO-mønsterets funktionalitet, da mønsteret blot bliver endnu et lag af abstraktion og kompleksitet implementeret oven på det, der leveres af JPA's enhedsadministrator.

Det er utvivlsomt, at det i nogle scenarier er sandt. Ikke desto mindre, nogle gange vil vi kun udsætte nogle få domænespecifikke metoder til vores ansøgning til vores ansøgning om enhedsadministratorens API. I sådanne tilfælde har DAO-mønsteret sin plads.

3.1. Det JpaUserDao Klasse

Med det sagt, lad os oprette en ny implementering af Dao interface, så vi kan se, hvordan det kan indkapsle den funktionalitet, som JPA's enhedsadministrator giver ud af boksen:

offentlig klasse JpaUserDao implementerer Dao {private EntityManager entityManager; // standardkonstruktører @ Override public Valgfri get (lang id) {return Optional.ofNullable (entityManager.find (User.class, id)); } @Override public List getAll () {Query query = entityManager.createQuery ("SELECT e FROM User e"); returner forespørgsel.getResultList (); } @ Overstyr offentlig ugyldig gem (brugerbruger) {executeInsideTransaction (entityManager -> entityManager.persist (bruger)); } @Override offentlig ugyldig opdatering (brugerbruger, streng [] params) {user.setName (Objects.requireNonNull (params [0], "Navn kan ikke være nul")); user.setEmail (Objects.requireNonNull (params [1], "E-mail kan ikke være nul")); executeInsideTransaction (entityManager -> entityManager.merge (bruger)); } @ Overstyr offentlig tomrumslet (brugerbruger) {executeInsideTransaction (entityManager -> entityManager.remove (bruger)); } privat tomrum executeInsideTransaction (Forbrugerhandling) {EntityTransaction tx = entityManager.getTransaction (); prøv {tx.begin (); action.accept (entityManager); tx.commit (); } fange (RuntimeException e) {tx.rollback (); smide e; }}}

Det JpaUserDao klasse er i stand til at arbejde med enhver relationsdatabase, der understøttes af JPA-implementeringen.

Desuden, hvis vi ser nøje på klassen, vil vi indse, hvordan brugen af ​​komposition og afhængighedsinjektion tillader os kun at kalde de enhedsadministratormetoder, der kræves af vores applikation.

Kort sagt, vi har en domænespecifik skræddersyet API snarere end hele enhedsadministratorens API.

3.2. Refactoring af Bruger Klasse

I dette tilfælde bruger vi Dvaletilstand som JPA-standardimplementering, og derfor omformulerer vi Bruger klasse i overensstemmelse hermed:

@Entity @Table (navn = "brugere") offentlig klasse bruger {@Id @GeneratedValue (strategi = GenerationType.AUTO) privat lang id; privat strengnavn; privat streng e-mail; // standardkonstruktører / settere / getters}

3.3. Bootstrapping af en JPA Entity Manager Programmatisk

Forudsat at vi allerede har en fungerende forekomst af MySQL, der kører enten lokalt eller eksternt, og en databasetabel “Brugere” befolket med nogle brugeroptegnelser, er vi nødt til at få en JPA enhedsadministrator, så vi kan bruge JpaUserDao klasse til udførelse af CRUD-operationer i databasen.

I de fleste tilfælde opnår vi dette via det typiske “Vedholdenhed.xml” fil, som er standardmetoden.

I dette tilfælde tager vi en “Xml-mindre” nærme dig og få enhedsadministratoren med almindelig Java gennem Hibernate's handy EntityManagerFactoryBuilderImpl klasse.

Se denne artikel for en detaljeret forklaring på, hvordan du starter en JPA-implementering med Java.

3.4. Det Brugerapplikation Klasse

Lad os endelig omformulere initialen Brugerapplikation klasse, så det kan arbejde med en JpaUserDao forekomme og udføre CRUD-operationer på Bruger enheder:

offentlig klasse UserApplication {privat statisk Dao jpaUserDao; // standard konstruktører offentlig statisk ugyldig hoved (String [] args) {Bruger bruger1 = getUser (1); System.out.println (bruger1); updateUser (user1, new String [] {"Jake", "[email protected]"}); saveUser (ny bruger ("Monica", "[email protected]")); deleteUser (getUser (2)); getAllUsers (). forEach (bruger -> System.out.println (user.getName ())); } offentlig statisk bruger getUser (lang id) {Valgfri bruger = jpaUserDao.get (id); returner user.orElseGet (() -> ny bruger ("ikke-eksisterende bruger", "ingen e-mail")); } offentlig statisk liste getAllUsers () {return jpaUserDao.getAll (); } offentlig statisk ugyldig opdateringsbruger (brugerbruger, streng [] params) {jpaUserDao.update (bruger, params); } offentlig statisk ugyldig saveUser (brugerbruger) {jpaUserDao.save (bruger); } offentlig statisk ugyldig deleteUser (brugerbruger) {jpaUserDao.delete (bruger); }}

Selv når eksemplet faktisk er ret begrænset, forbliver det nyttigt til at demonstrere, hvordan man integrerer DAO-mønsterets funktionalitet med den, som enhedsadministratoren giver.

I de fleste applikationer er der en DI-ramme, som er ansvarlig for at injicere en JpaUserDao eksempel i Brugerapplikation klasse. For enkelheds skyld har vi udeladt detaljerne i denne proces.

Det mest relevante punkt at understrege her er hvordan det JpaUserDao klasse hjælper med at holde Brugerapplikation klasse fuldstændig agnostisk om, hvordan persistenslaget udfører CRUD-operationer.

Derudover kunne vi bytte MySQL med enhver anden RDBMS (og endda med en flad database) længere nede ad vejen, og stadig vil vores applikation fortsætte med at arbejde som forventet takket være abstraktionsniveauet fra Dao interface og enhedsadministrator.

4. Konklusion

I denne artikel tog vi et dybtgående kig på DAO-mønsterets nøglekoncepter, hvordan man implementerer det i Java, og hvordan man bruger det oven på JPAs enhedsadministrator.

Som sædvanligt er alle kodeeksempler vist i denne artikel tilgængelige på GitHub.