Introduktion til Jooq med Spring

1. Oversigt

Denne artikel introducerer Jooq Object Oriented Querying - Jooq - og en enkel måde at oprette den på i samarbejde med Spring Framework.

De fleste Java-applikationer har en eller anden form for SQL-persistens og får adgang til dette lag ved hjælp af værktøjer på højere niveau som JPA. Og mens det er nyttigt, har du i nogle tilfælde virkelig brug for et finere, mere nuanceret værktøj for at komme til dine data eller for faktisk at drage fordel af alt det underliggende DB har at tilbyde.

Jooq undgår nogle typiske ORM-mønstre og genererer kode, der giver os mulighed for at oprette typesafe-forespørgsler og få fuld kontrol over den genererede SQL via en ren og stærk flydende API.

Denne artikel fokuserer på Spring MVC. Vores artikel Spring Boot Support til jOOQ beskriver, hvordan du bruger jOOQ i Spring Boot.

2. Maven-afhængigheder

Følgende afhængigheder er nødvendige for at køre koden i denne vejledning.

2.1. jOOQ

 org.jooq jooq 3.2.14 

2.2. Forår

Der er flere forårsafhængigheder, der kræves for vores eksempel; for at gøre tingene enkle skal vi blot eksplicit inkludere to af dem i POM-filen:

 org.springframework spring-context 5.2.2.RELEASE org.springframework spring-jdbc 5.2.2.RELEASE 

2.3. Database

For at gøre tingene lette for vores eksempel bruger vi den indbyggede H2-database:

 com.h2database h2 1.4.191 

3. Kodegenerering

3.1. Databasestruktur

Lad os introducere den databasestruktur, vi vil arbejde med i hele denne artikel. Antag, at vi har brug for at oprette en database for en udgiver, der skal gemme information om de bøger og forfattere, de administrerer, hvor en forfatter kan skrive mange bøger, og en bog kan være co-skrevet af mange forfattere.

For at gøre det enkelt genererer vi kun tre tabeller: Bestil til bøger, forfatter for forfattere og en anden tabel kaldet author_book at repræsentere forholdet mellem mange og mange mellem forfattere og bøger. Det forfatter tabellen har tre kolonner: id, fornavnog efternavn. Det Bestil tabellen indeholder kun en titel kolonne og id primærnøgle.

Følgende SQL-forespørgsler, der er gemt i intro_schema.sql ressourcefil, udføres mod den database, vi allerede har oprettet før for at oprette de nødvendige tabeller og udfylde dem med eksempeldata:

DROP TABEL, HVIS DET FINDER author_book, author, book; Opret TABELforfatter (id INT IKKE NULL PRIMÆR NØGLE, fornavn VARCHAR (50), efternavn VARCHAR (50) IKKE NULL); OPRET TABELbog (id INT IKKE NULL PRIMÆR NØGLE, titel VARCHAR (100) IKKE NULL); OPRET TABEL author_book (author_id INT NOT NULL, book_id INT NOT NULL, PRIMARY KEY (author_id, book_id), CONSTRAINT fk_ab_author FOREIGN KEY (author_id) REFERENCES author (id) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT f (bog) (id)); INDSÆT I FORfatterVÆRDIER (1, 'Kathy', 'Sierra'), (2, 'Bert', 'Bates'), (3, 'Bryan', 'Basham'); INDSÆT I BOGVÆRDIER (1, 'Head First Java'), (2, 'Head First Servlets and JSP'), (3, 'OCA / OCP Java SE 7 Programmer'); INDSÆT I Forfatterbog VÆRDIER (1, 1), (1, 3), (2, 1);

3.2. Egenskaber Maven Plugin

Vi bruger tre forskellige Maven-plugins til at generere Jooq-koden. Den første af disse er Properties Maven-pluginet.

Dette plugin bruges til at læse konfigurationsdata fra en ressourcefil. Det er ikke påkrævet, da dataene kan føjes direkte til POM, men det er en god ide at administrere egenskaberne eksternt.

I dette afsnit definerer vi egenskaber til databaseforbindelser, inklusive JDBC-driverklasse, database-URL, brugernavn og adgangskode, i en fil med navnet intro_config.properties. Eksternalisering af disse egenskaber gør det let at skifte database eller bare ændre konfigurationsdataene.

Det læse-projekt-egenskaber målet med dette plugin skal være bundet til en tidlig fase, så konfigurationsdataene kan forberedes til brug af andre plugins. I dette tilfælde er det bundet til initialisere fase:

 org.codehaus.mojo egenskaber-maven-plugin 1.0.0 initialiser læse-projekt-egenskaber src / main / resources / intro_config.properties 

3.3. SQL Maven-plugin

SQL Maven-pluginet bruges til at udføre SQL-sætninger for at oprette og udfylde databasetabeller. Det vil gøre brug af de egenskaber, der er udvundet fra intro_config.properties fil ved hjælp af Properties Maven-pluginet, og tag SQL-sætningerne fra intro_schema.sql ressource.

SQL Maven-pluginet er konfigureret som nedenfor:

 org.codehaus.mojo sql-maven-plugin 1.5 initialiser udfør $ {db.driver} $ {db.url} $ {db.username} $ {db.password} src / main / resources / intro_schema.sql com.h2database h2 1.4.191 

Bemærk, at dette plugin skal placeres senere end Properties Maven-pluginet i POM-filen, da deres eksekveringsmål begge er bundet til den samme fase, og Maven udfører dem i den rækkefølge, de er angivet.

3.4. jOOQ Codegen-plugin

Jooq Codegen Plugin genererer Java-kode fra en databasetabelstruktur. Dens frembringe mål skal være bundet til generere kilder fase for at sikre den korrekte udførelsesrækkefølge. Plugin-metadata ser ud som følger:

 org.jooq jooq-codegen-maven $ {org.jooq.version} genererer-kilder genererer $ {db.driver} $ {db.url} $ {db.username} $ {db.password} com.baeldung.jooq. introduktion.db src / main / java 

3.5. Genererer kode

For at afslutte processen med generering af kildekode skal vi køre Maven generere kilder fase. I Eclipse kan vi gøre dette ved at højreklikke på projektet og vælge Løb som –>Maven genererer kilder. Når kommandoen er afsluttet, kildefiler, der svarer til forfatter, Bestil, author_book tabeller (og flere andre til understøttende klasser) genereres.

Lad os grave i tabelklasser for at se, hvad Jooq producerede. Hver klasse har et statisk felt med samme navn som klassen, bortset fra at alle bogstaver i navnet er store. Følgende er kodestykker taget fra de genererede klassers definitioner:

Det Forfatter klasse:

offentlig klasse Forfatter udvider TableImpl {offentlig statisk endelig Forfatter FORfatter = ny forfatter (); // andre klassemedlemmer}

Det Bestil klasse:

public class Book udvider TableImpl {public static final Bog BOOK = ny bog (); // andre klassemedlemmer}

Det Forfatterbog klasse:

public class AuthorBook udvider TableImpl {public static final AuthorBook AUTHOR_BOOK = ny AuthorBook (); // andre klassemedlemmer}

De forekomster, der refereres til af disse statiske felter, fungerer som dataadgangsobjekter til at repræsentere de tilsvarende tabeller, når du arbejder med andre lag i et projekt.

4. Forårskonfiguration

4.1. Oversættelse af jOOQ-undtagelser til foråret

For at gøre undtagelser fra Jooq-udførelse i overensstemmelse med Spring-understøttelse af databaseadgang, er vi nødt til at oversætte dem til undertyper af DataAccessException klasse.

Lad os definere en implementering af ExecuteListener interface til at konvertere undtagelser:

offentlig klasse ExceptionTranslator udvider DefaultExecuteListener {offentlig ugyldig undtagelse (ExecuteContext context) {SQLDialect dialect = context.configuration (). dialect (); SQLExceptionTranslator translator = ny SQLErrorCodeSQLExceptionTranslator (dialect.name ()); context.exception (oversætter .translate ("Adgang til database ved hjælp af Jooq", context.sql (), context.sqlException ())); }}

Denne klasse vil blive brugt af forårets applikationskontekst.

4.2. Konfiguration af foråret

Dette afsnit gennemgår trin til at definere en PersistenceContext der indeholder metadata og bønner, der skal bruges i forbindelse med Spring-applikationen.

Lad os komme i gang ved at anvende nødvendige kommentarer til klassen:

  • @Konfiguration: Få klassen til at blive anerkendt som en beholder til bønner
  • @ComponentScan: Konfigurer scanningsdirektiver, inklusive værdi mulighed for at erklære en række pakkenavne for at søge efter komponenter. I denne vejledning er den pakke, der skal søges, den, der genereres af Jooq Codegen Maven-pluginet
  • @EnableTransactionManagement: Aktivér, at transaktioner administreres af Spring
  • @PropertySource: Angiv placeringen af ​​egenskabsfilerne, der skal indlæses. Værdien i denne artikel peger på filen, der indeholder konfigurationsdata og dialekt af databasen, hvilket tilfældigvis er den samme fil, der er nævnt i underafsnit 4.1.
@Configuration @ComponentScan ({"com.baeldung.Jooq.introduction.db.public_.tables"}) @EnableTransactionManagement @PropertySource ("classpath: intro_config.properties") offentlig klasse PersistenceContext {// Andre erklæringer}

Brug derefter en Miljø objekt for at hente konfigurationsdataene, som derefter bruges til at konfigurere Datakilde bønne:

@Autowired privat miljø miljø; @Bean public DataSource dataSource () {JdbcDataSource dataSource = new JdbcDataSource (); dataSource.setUrl (miljø.getRequiredProperty ("db.url")); dataSource.setUser (miljø.getRequiredProperty ("db.username")); dataSource.setPassword (miljø.getRequiredProperty ("db.password"));
 returnere datakilde; }

Nu definerer vi flere bønner til at arbejde med databaseadgangsoperationer:

@Bean offentlig TransactionAwareDataSourceProxy-transaktionAwareDataSource () {returner ny TransactionAwareDataSourceProxy (dataSource ()); } @Bean public DataSourceTransactionManager transactionManager () {returner ny DataSourceTransactionManager (dataSource ()); } @Bean public DataSourceConnectionProvider connectionProvider () {returner ny DataSourceConnectionProvider (transactionAwareDataSource ()); } @Bean offentlig ExceptionTranslator exceptionTransformer () {returner ny ExceptionTranslator (); } @Bean offentlig DefaultDSLContext dsl () {returner ny DefaultDSLContext (konfiguration ()); }

Endelig leverer vi en Jooq Konfiguration implementering og erklære det som en springbønne, der skal bruges af DSLContext klasse:

@Bean offentlig DefaultConfiguration-konfiguration () {DefaultConfiguration JooqConfiguration = ny DefaultConfiguration (); jooqConfiguration.set (connectionProvider ()); jooqConfiguration.set (ny DefaultExecuteListenerProvider (exceptionTransformer ())); Streng sqlDialectName = miljø.getRequiredProperty ("jooq.sql.dialect"); SQLDialect dialekt = SQLDialect.valueOf (sqlDialectName); jooqConfiguration.set (dialekt); returner jooqConfiguration; }

5. Brug af jOOQ med forår

Dette afsnit demonstrerer brugen af ​​Jooq i almindelige databaseadgangsforespørgsler. Der er to tests, en til commit og en til tilbageførsel, for hver type "skriv" -operation, herunder indsættelse, opdatering og sletning af data. Brugen af ​​"læs" -operation illustreres, når du vælger data til bekræftelse af "skriv" -forespørgslerne.

Vi begynder med at erklære en automatisk kabelforbindelse DSLContext objekt og forekomster af Jooq-genererede klasser, der skal bruges af alle testmetoder:

@Autowired privat DSLContext dsl; Forfatterforfatter = Forfatter.AUTHOR; Bogbog = Bog.BOK; AuthorBook authorBook = AuthorBook.AUTHOR_BOOK;

5.1. Indsættelse af data

Det første trin er at indsætte data i tabeller:

dsl.insertInto (author) .set (author.ID, 4) .set (author.FIRST_NAME, "Herbert") .set (author.LAST_NAME, "Schildt"). execute (); dsl.insertInto (book) .set (book.ID, 4) .set (book.TITLE, "A Beginner's Guide"). execute (); dsl.insertInto (authorBook) .set (authorBook.AUTHOR_ID, 4). set (authorBook.BOOK_ID, 4) .execute ();

EN VÆLG forespørgsel for at udtrække data:

Resultat resultat = dsl .select (author.ID, author.LAST_NAME, DSL.count ()) .fra (author) .join (authorBook). on (author.ID.equal (authorBook.AUTHOR_ID)) .join (book). på (authorBook.BOOK_ID.equal (book.ID)) .groupBy (author.LAST_NAME) .fetch ();

Ovenstående forespørgsel producerer følgende output:

+ ---- + --------- + ----- + | ID | LAST_NAME | antal | + ---- + --------- + ----- + | 1 | Sierra | 2 | | 2 | Bates | 1 | | 4 | Schildt | 1 | + ---- + --------- + ----- +

Resultatet bekræftes af Hævde API:

assertEquals (3, result.size ()); assertEquals ("Sierra", result.getValue (0, forfatter.LAST_NAME)); assertEquals (Integer.valueOf (2), result.getValue (0, DSL.count ())); assertEquals ("Schildt", result.getValue (2, forfatter.LAST_NAME)); assertEquals (Integer.valueOf (1), result.getValue (2, DSL.count ()));

Når en fejl opstår på grund af en ugyldig forespørgsel, kastes en undtagelse, og transaktionen rulles tilbage. I det følgende eksempel er INDSÆT forespørgsel overtræder en fremmed nøglebegrænsning, hvilket resulterer i en undtagelse:

@Test (forventet = DataAccessException.class) offentlig ugyldighed givenInvalidData_whenInserting_thenFail () {dsl.insertInto (authorBook) .set (authorBook.AUTHOR_ID, 4). Set (authorBook.BOOK_ID, 5). Execute (); }

5.2. Opdatering af data

Lad os nu opdatere de eksisterende data:

dsl.update (forfatter) .set (forfatter.LAST_NAME, "Baeldung"). hvor (author.ID.equal (3)). udfør (); dsl.update (bog) .set (book.TITLE, "Byg din REST API med Spring") .where (book.ID.equal (3)) .execute (); dsl.insertInto (authorBook) .set (authorBook.AUTHOR_ID, 3). set (authorBook.BOOK_ID, 3) .execute ();

Få de nødvendige data:

Resultat resultat = dsl .select (author.ID, author.LAST_NAME, book.TITLE) .fra (author) .join (authorBook) .on (author.ID.equal (authorBook.AUTHOR_ID)) .join (book) .on ( authorBook.BOOK_ID.equal (book.ID)) .where (author.ID.equal (3)) .fetch ();

Outputtet skal være:

+ ---- + --------- + ---------------------------------- + | ID | LAST_NAME | TITEL | + ---- + --------- + ---------------------------------- + | 3 | Baeldung | Opbygning af din REST API med Spring | + ---- + --------- + ---------------------------------- +

Følgende test bekræfter, at Jooq fungerede som forventet:

assertEquals (1, result.size ()); assertEquals (Integer.valueOf (3), result.getValue (0, author.ID)); assertEquals ("Baeldung", result.getValue (0, forfatter.LAST_NAME)); assertEquals ("Opbygning af din REST API med Spring", result.getValue (0, book.TITLE));

I tilfælde af en fiasko kastes en undtagelse, og transaktionen rulles tilbage, hvilket vi bekræfter med en test:

@Test (forventet = DataAccessException.class) offentlig ugyldighed givenInvalidData_whenUpdating_thenFail () {dsl.update (authorBook). Sæt (authorBook.AUTHOR_ID, 4). Sæt (authorBook.BOOK_ID, 5). Udfør (); }

5.3. Sletning af data

Følgende metode sletter nogle data:

dsl.delete (forfatter). hvor (author.ID.lt (3)). udfør ();

Her er forespørgslen for at læse den berørte tabel:

Resultat resultat = dsl. Vælg (forfatter.ID, forfatter.FIRST_NAME, forfatter.LAST_NAME) .fra (forfatter) .hent ();

Forespørgselsoutput:

+ ---- + ---------- + --------- + | ID | FIRST_NAME | LAST_NAME | + ---- + ---------- + --------- + | 3 | Bryan | Basham | + ---- + ---------- + --------- +

Følgende test bekræfter sletningen:

assertEquals (1, result.size ()); assertEquals ("Bryan", result.getValue (0, forfatter.FIRST_NAME)); assertEquals ("Basham", result.getValue (0, forfatter.LAST_NAME));

På den anden side, hvis en forespørgsel er ugyldig, kaster den en undtagelse, og transaktionen rulles tilbage. Følgende test viser, at:

@Test (forventet = DataAccessException.class) offentlig ugyldighed givenInvalidData_whenDeleting_thenFail () {dsl.delete (bog) .where (book.ID.equal (1)) .execute (); }

6. Konklusion

Denne vejledning introducerede det grundlæggende i Jooq, et Java-bibliotek til arbejde med databaser. Det dækkede trinene til generering af kildekode fra en databasestruktur og hvordan man interagerer med den database ved hjælp af de nyoprettede klasser.

Implementeringen af ​​alle disse eksempler og kodestykker kan findes i et GitHub-projekt.