Einführung in jOOQ mit Spring

Einführung in Jooq mit Spring

1. Überblick

In diesem Artikel wird Jooq Object Oriented Querying (Jooq) vorgestellt und auf einfache Weise in Zusammenarbeit mit dem Spring Framework eingerichtet.

Die meisten Java-Anwendungen verfügen über eine gewisse SQL-Persistenz und greifen mithilfe übergeordneter Tools wie JPA auf diese Ebene zu. Und obwohl dies nützlich ist, benötigen Sie in einigen Fällen wirklich ein feineres, differenzierteres Tool, um auf Ihre Daten zuzugreifen oder tatsächlich alles zu nutzen, was die zugrunde liegende Datenbank zu bieten hat.

Jooq vermeidet einige typische ORM-Muster und generiert Code, mit dem wir typsichere Abfragen erstellen und über eine saubere und leistungsstarke, flüssige API die vollständige Kontrolle über das generierte SQL erhalten können.

2. Maven-Abhängigkeiten

Die folgenden Abhängigkeiten sind erforderlich, um den Code in diesem Lernprogramm auszuführen.

2.1. jOOQ


    org.Jooq
    jooq
    3.7.3

2.2. Frühling

Für unser Beispiel sind mehrere Frühlingsabhängigkeiten erforderlich. Zur Vereinfachung müssen jedoch nur zwei davon explizit in die POM-Datei aufgenommen werden:


    org.springframework
    spring-context
    4.2.5.RELEASE


    org.springframework
    spring-jdbc
    4.2.5.RELEASE

2.3. Datenbank

Um die Sache für unser Beispiel zu vereinfachen, werden wir die eingebettete H2-Datenbank verwenden:


    com.h2database
    h2
    1.4.191

3. Codegenerierung

3.1. Datenbankstruktur

Lassen Sie uns die Datenbankstruktur vorstellen, mit der wir in diesem Artikel arbeiten werden. Angenommen, wir müssen eine Datenbank für einen Verlag erstellen, in der Informationen zu den von ihm verwalteten Büchern und Autoren gespeichert werden. Dabei kann ein Autor viele Bücher schreiben und ein Buch von vielen Autoren mitgeschrieben werden.

Um es einfach zu machen, werden wir nur drei Tabellen generieren:book für Bücher,author für Autoren und eine andere Tabelle namensauthor_book, um die Viele-zu-Viele-Beziehung zwischen Autoren und darzustellen Bücher. Die Tabelleauthor enthält drei Spalten:id,first_name undlast_name. Die Tabellebook enthält nur eine Spaltetitleundid) s Primärschlüssel.

Die folgenden SQL-Abfragen, die in der Ressourcendateiintro_schema.sqlgespeichert sind, werden für die bereits zuvor eingerichtete Datenbank ausgeführt, um die erforderlichen Tabellen zu erstellen und sie mit Beispieldaten zu füllen:

DROP TABLE IF EXISTS author_book, author, book;

CREATE TABLE author (
  id             INT          NOT NULL PRIMARY KEY,
  first_name     VARCHAR(50),
  last_name      VARCHAR(50)  NOT NULL
);

CREATE TABLE book (
  id             INT          NOT NULL PRIMARY KEY,
  title          VARCHAR(100) NOT NULL
);

CREATE TABLE 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 fk_ab_book       FOREIGN KEY (book_id)    REFERENCES book   (id)
);

INSERT INTO author VALUES
  (1, 'Kathy', 'Sierra'),
  (2, 'Bert', 'Bates'),
  (3, 'Bryan', 'Basham');

INSERT INTO book VALUES
  (1, 'Head First Java'),
  (2, 'Head First Servlets and JSP'),
  (3, 'OCA/OCP Java SE 7 Programmer');

INSERT INTO author_book VALUES (1, 1), (1, 3), (2, 1);

3.2. Eigenschaften Maven Plugin

Wir werden drei verschiedene Maven-Plugins verwenden, um den Jooq-Code zu generieren. Das erste davon ist das Properties Maven Plugin.

Dieses Plugin wird zum Lesen von Konfigurationsdaten aus einer Ressourcendatei verwendet. Dies ist nicht erforderlich, da die Daten möglicherweise direkt zum POM hinzugefügt werden. Es empfiehlt sich jedoch, die Eigenschaften extern zu verwalten.

In diesem Abschnitt definieren wir Eigenschaften für Datenbankverbindungen, einschließlich der JDBC-Treiberklasse, der Datenbank-URL, des Benutzernamens und des Kennworts, in einer Datei mit dem Namenintro_config.properties. Das Externalisieren dieser Eigenschaften erleichtert das Wechseln der Datenbank oder nur das Ändern der Konfigurationsdaten.

Dasread-project-properties-Ziel dieses Plugins sollte an eine frühe Phase gebunden sein, damit die Konfigurationsdaten für die Verwendung durch andere Plugins vorbereitet werden können. In diesem Fall ist es an dieinitialize-Phase gebunden:


    org.codehaus.mojo
    properties-maven-plugin
    1.0.0
    
        
            initialize
            
                read-project-properties
            
            
                
                    src/main/resources/intro_config.properties
                
            
        
    

3.3. SQL Maven Plugin

Das SQL Maven-Plugin wird zum Ausführen von SQL-Anweisungen zum Erstellen und Auffüllen von Datenbanktabellen verwendet. Es werden die Eigenschaften verwendet, die vom Properties Maven-Plugin aus der Dateiintro_config.properties extrahiert wurden, und die SQL-Anweisungen werden aus der Ressourceintro_schema.sqlübernommen.

Das SQL Maven Plugin ist wie folgt konfiguriert:


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

Beachten Sie, dass dieses Plugin später als das Properties Maven-Plugin in der POM-Datei platziert werden muss, da ihre Ausführungsziele an dieselbe Phase gebunden sind und Maven sie in der angegebenen Reihenfolge ausführt.

3.4. jOOQ Codegen Plugin

Das Jooq Codegen Plugin generiert Java-Code aus einer Datenbanktabellenstruktur. Das Ziel vongenerateollte an die Phase vongenerate-sourcesgebunden sein, um die korrekte Ausführungsreihenfolge sicherzustellen. Die Plugin-Metadaten sehen folgendermaßen aus:


    org.Jooq
    jooq-codegen-maven
    ${org.jooq.version}
    
        
            generate-sources
            
                generate
            
            
                
                    ${db.driver}
                    ${db.url}
                    ${db.username}
                    ${db.password}
                
                
                    
                        com.example.jooq.introduction.db
                        src/main/java
                    
                
            
        
    

3.5. Code generieren

Um den Prozess der Quellcodegenerierung abzuschließen, müssen wir die Phase von Mavengenerate-sourcesausführen. In Eclipse können Sie dies tun, indem Sie mit der rechten Maustaste auf das Projekt klicken undRun As ->Maven generate-sources auswählen. Nach Abschluss des Befehls werden Quelldateien generiert, die den Tabellenauthor,book,author_book (und mehreren anderen für unterstützende Klassen) entsprechen.

Schauen wir uns die Tabellenklassen an, um zu sehen, was Jooq produziert hat. Jede Klasse hat ein statisches Feld mit demselben Namen wie die Klasse, mit der Ausnahme, dass alle Buchstaben im Namen in Großbuchstaben geschrieben werden. Die folgenden Codeausschnitte stammen aus den Definitionen der generierten Klassen:

Die KlasseAuthor:

public class Author extends TableImpl {
    public static final Author AUTHOR = new Author();

    // other class members
}

Die KlasseBook:

public class Book extends TableImpl {
    public static final Book BOOK = new Book();

    // other class members
}

Die KlasseAuthorBook:

public class AuthorBook extends TableImpl {
    public static final AuthorBook AUTHOR_BOOK = new AuthorBook();

    // other class members
}

Die von diesen statischen Feldern referenzierten Instanzen dienen als Datenzugriffsobjekte, um die entsprechenden Tabellen darzustellen, wenn Sie mit anderen Layern in einem Projekt arbeiten.

4. Federkonfiguration

4.1. JOOQ-Ausnahmen in den Frühling übersetzen

Um Ausnahmen von der Jooq-Ausführung mit der Spring-Unterstützung für den Datenbankzugriff in Einklang zu bringen, müssen sie in Untertypen der KlasseDataAccessExceptionübersetzt werden.

Definieren wir eine Implementierung derExecuteListener-Schnittstelle zum Konvertieren von Ausnahmen:

public class ExceptionTranslator extends DefaultExecuteListener {
    public void exception(ExecuteContext context) {
        SQLDialect dialect = context.configuration().dialect();
        SQLExceptionTranslator translator
          = new SQLErrorCodeSQLExceptionTranslator(dialect.name());
        context.exception(translator
          .translate("Access database using Jooq", context.sql(), context.sqlException()));
    }
}

Diese Klasse wird vom Spring-Anwendungskontext verwendet.

4.2. Frühling konfigurieren

In diesem Abschnitt werden Schritte zum Definieren vonPersistenceContext ausgeführt, die Metadaten und Beans enthalten, die im Spring-Anwendungskontext verwendet werden sollen.

Beginnen wir mit dem Anwenden der erforderlichen Anmerkungen auf die Klasse:

  • @Configuration: Legt fest, dass die Klasse als Container für Bohnen erkannt wird

  • @ComponentScan: Konfigurieren Sie Scananweisungen, einschließlich der Optionvalue, um ein Array von Paketnamen für die Suche nach Komponenten zu deklarieren. In diesem Tutorial wird nach dem Paket gesucht, das vom Jooq Codegen Maven-Plugin generiert wurde

  • @EnableTransactionManagement: Aktivieren Sie die Verwaltung von Transaktionen durch Spring

  • @PropertySource: Geben Sie die Speicherorte der zu ladenden Eigenschaftendateien an. Der Wert in diesem Artikel verweist auf die Datei, die Konfigurationsdaten und den Dialekt der Datenbank enthält. Dabei handelt es sich zufällig um dieselbe Datei, die in Unterabschnitt 4.1 erwähnt wurde.

@Configuration
@ComponentScan({"com.example.Jooq.introduction.db.public_.tables"})
@EnableTransactionManagement
@PropertySource("classpath:intro_config.properties")
public class PersistenceContext {
    // Other declarations
}

Verwenden Sie als Nächstes einEnvironment-Objekt, um die Konfigurationsdaten abzurufen, die dann zum Konfigurieren derDataSource-Bean verwendet werden:

@Autowired
private Environment environment;

@Bean
public DataSource dataSource() {
    JdbcDataSource dataSource = new JdbcDataSource();

    dataSource.setUrl(environment.getRequiredProperty("db.url"));
    dataSource.setUser(environment.getRequiredProperty("db.username"));
    dataSource.setPassword(environment.getRequiredProperty("db.password"));
    return dataSource;
}

Nun definieren wir mehrere Beans für die Arbeit mit Datenbankzugriffsoperationen:

@Bean
public TransactionAwareDataSourceProxy transactionAwareDataSource() {
    return new TransactionAwareDataSourceProxy(dataSource());
}

@Bean
public DataSourceTransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
}

@Bean
public DataSourceConnectionProvider connectionProvider() {
    return new DataSourceConnectionProvider(transactionAwareDataSource());
}

@Bean
public ExceptionTranslator exceptionTransformer() {
    return new ExceptionTranslator();
}

@Bean
public DefaultDSLContext dsl() {
    return new DefaultDSLContext(configuration());
}

Schließlich stellen wir die Implementierung von JooqConfigurationbereit und deklarieren sie als Spring Bean, die von der KlasseDSLContextverwendet werden soll:

@Bean
public DefaultConfiguration configuration() {
    DefaultConfiguration JooqConfiguration = new DefaultConfiguration();
    jooqConfiguration.set(connectionProvider());
    jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer()));

    String sqlDialectName = environment.getRequiredProperty("jooq.sql.dialect");
    SQLDialect dialect = SQLDialect.valueOf(sqlDialectName);
    jooqConfiguration.set(dialect);

    return jooqConfiguration;
}

5. Verwenden von jOOQ mit Spring

Dieser Abschnitt zeigt die Verwendung von Jooq in allgemeinen Datenbankzugriffsabfragen. Es gibt zwei Tests, einen für das Festschreiben und einen für das Zurücksetzen, für jeden Typ von "Schreib" -Operation, einschließlich Einfügen, Aktualisieren und Löschen von Daten. Die Verwendung der Leseoperation wird bei der Auswahl von Daten zur Überprüfung der Schreibabfragen veranschaulicht.

Wir beginnen mit der Deklaration eines automatisch verdrahtetenDSLContext-Objekts und von Instanzen von Jooq-generierten Klassen, die von allen Testmethoden verwendet werden sollen:

@Autowired
private DSLContext dsl;

Author author = Author.AUTHOR;
Book book = Book.BOOK;
AuthorBook authorBook = AuthorBook.AUTHOR_BOOK;

5.1. Daten einfügen

Der erste Schritt besteht darin, Daten in Tabellen einzufügen:

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();

ASELECT Abfrage zum Extrahieren von Daten:

Result> result = dsl
  .select(author.ID, author.LAST_NAME, DSL.count())
  .from(author)
  .join(authorBook)
  .on(author.ID.equal(authorBook.AUTHOR_ID))
  .join(book)
  .on(authorBook.BOOK_ID.equal(book.ID))
  .groupBy(author.LAST_NAME)
  .fetch();

Die obige Abfrage erzeugt die folgende Ausgabe:

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

Das Ergebnis wird durch dieAssert API bestätigt:

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

Wenn ein Fehler aufgrund einer ungültigen Abfrage auftritt, wird eine Ausnahme ausgelöst und die Transaktion wird zurückgesetzt. Im folgenden Beispiel verletzt die Abfrage vonINSERTeine Fremdschlüsseleinschränkung, was zu einer Ausnahme führt:

@Test(expected = DataAccessException.class)
public void givenInvalidData_whenInserting_thenFail() {
    dsl.insertInto(authorBook)
      .set(authorBook.AUTHOR_ID, 4)
      .set(authorBook.BOOK_ID, 5)
      .execute();
}

5.2. Daten aktualisieren

Aktualisieren wir nun die vorhandenen Daten:

dsl.update(author)
  .set(author.LAST_NAME, "example")
  .where(author.ID.equal(3))
  .execute();
dsl.update(book)
  .set(book.TITLE, "Building your REST API with Spring")
  .where(book.ID.equal(3))
  .execute();
dsl.insertInto(authorBook)
  .set(authorBook.AUTHOR_ID, 3)
  .set(authorBook.BOOK_ID, 3)
  .execute();

Holen Sie sich die notwendigen Daten:

Result> result = dsl
  .select(author.ID, author.LAST_NAME, book.TITLE)
  .from(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();

Die Ausgabe sollte sein:

+----+---------+----------------------------------+
|  ID|LAST_NAME|TITLE                             |
+----+---------+----------------------------------+
|   3|example |Building your REST API with Spring|
+----+---------+----------------------------------+

Mit dem folgenden Test wird überprüft, ob Jooq wie erwartet funktioniert hat:

assertEquals(1, result.size());
assertEquals(Integer.valueOf(3), result.getValue(0, author.ID));
assertEquals("example", result.getValue(0, author.LAST_NAME));
assertEquals("Building your REST API with Spring", result.getValue(0, book.TITLE));

Im Falle eines Fehlers wird eine Ausnahme ausgelöst und die Transaktion rückgängig gemacht, was wir mit einem Test bestätigen:

@Test(expected = DataAccessException.class)
public void givenInvalidData_whenUpdating_thenFail() {
    dsl.update(authorBook)
      .set(authorBook.AUTHOR_ID, 4)
      .set(authorBook.BOOK_ID, 5)
      .execute();
}

5.3. Daten löschen

Die folgende Methode löscht einige Daten:

dsl.delete(author)
  .where(author.ID.lt(3))
  .execute();

Hier ist die Abfrage zum Lesen der betroffenen Tabelle:

Result> result = dsl
  .select(author.ID, author.FIRST_NAME, author.LAST_NAME)
  .from(author)
  .fetch();

Die Abfrage Ausgabe:

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

Der folgende Test bestätigt die Löschung:

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

Wenn eine Abfrage hingegen ungültig ist, wird eine Ausnahme ausgelöst und die Transaktion wird zurückgesetzt. Der folgende Test wird das beweisen:

@Test(expected = DataAccessException.class)
public void givenInvalidData_whenDeleting_thenFail() {
    dsl.delete(book)
      .where(book.ID.equal(1))
      .execute();
}

6. Fazit

In diesem Tutorial wurden die Grundlagen von Jooq vorgestellt, einer Java-Bibliothek zum Arbeiten mit Datenbanken. Es wurden die Schritte zum Generieren von Quellcode aus einer Datenbankstruktur und zum Interagieren mit dieser Datenbank unter Verwendung der neu erstellten Klassen beschrieben.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie ina GitHub project.