Java >> Java Tutorial >  >> Tag >> Spring

Verwendung von jOOQ mit Spring:Konfiguration

Ich hatte meinen Anteil an Leistungsproblemen, die durch ORMs verursacht wurden. Obwohl ich zugeben muss, dass die meisten dieser Probleme wirklich von Ihnen verursacht wurden, habe ich angefangen zu glauben, dass es sich nicht lohnt, ORMs in Nur-Lese-Operationen zu verwenden.

Ich fing an, nach alternativen Wegen zu suchen, um diese Operationen zu implementieren.

So bin ich auf jOOQ gestoßen, das Folgendes besagt:

jOOQ generiert Java-Code aus Ihrer Datenbank und lässt Sie über seine fließende API typsichere SQL-Abfragen erstellen.

Das sieht sehr interessant aus. Deshalb habe ich beschlossen, jOOQ eine Chance zu geben und meine Erkenntnisse mit Ihnen zu teilen.

Dieser Blogbeitrag ist der erste Teil meiner Serie Using jOOQ with Spring. Es beschreibt, wie wir die erforderlichen Abhängigkeiten erhalten und den Anwendungskontext unserer Anwendung konfigurieren können.

Fangen wir an.

Erforderliche Abhängigkeiten mit Maven erhalten

Die Abhängigkeiten unserer Anwendung sind:

  • Spring Framework 4.1.2.RELEASE. An dieser Stelle verwendet unser Beispiel das aop , Bohnen , Kern , Kontext , Kontextunterstützung , jdbc , und tx Module.
  • cglib 3.1.
  • BoneCP 0.8.0. Wir verwenden BoneCP als Verbindungspool unserer Beispielanwendung.
  • jOOQ 3.4.4.
  • H2 1.3.174. Wir verwenden H2 als Datenbank unserer Beispielanwendung.

Der relevante Teil der pom.xml Datei sieht wie folgt aus:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-beans</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>4.1.2.RELEASE</version>
</Dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context-support</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-expression</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-tx</artifactId>
	<version>4.1.2.RELEASE</version>
</dependency>
        
<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>3.1</version>
</dependency>

<dependency>
	<groupId>com.jolbox</groupId>
	<artifactId>bonecp</artifactId>
	<version>0.8.0.RELEASE</version>
</dependency>

<dependency>
	<groupId>org.jooq</groupId>
	<artifactId>jooq</artifactId>
	<version>3.4.4</version>
</dependency>

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>1.3.174</version>
</dependency>

Lassen Sie uns weitermachen und herausfinden, wie wir die von jOOQ ausgelösten Ausnahmen in Spring DataAccessExceptions umwandeln können .

Umwandlung von jOOQ-Ausnahmen in Spring DataAccessExceptions

Warum sollten wir die von jOOQ ausgelösten Ausnahmen in Spring DataAccessExceptions umwandeln? ?

Ein Grund dafür ist, dass wir möchten, dass unsere Integration genauso funktioniert wie die DAO-Unterstützung von Spring Framework. Ein wesentlicher Bestandteil dieser Unterstützung ist eine konsistente Ausnahmehierarchie:

Spring bietet eine bequeme Übersetzung von technologiespezifischen Ausnahmen wie SQLException zu einer eigenen Ausnahmeklassenhierarchie mit der DataAccessException als Root-Ausnahme. Diese Ausnahmen schließen die ursprüngliche Ausnahme ein, sodass niemals die Gefahr besteht, dass Informationen darüber verloren gehen, was schief gelaufen sein könnte.

Mit anderen Worten, wenn wir möchten, dass unsere Anwendung "ein guter Bürger" ist, ist es sinnvoll sicherzustellen, dass unsere Konfiguration die von jOOQ ausgelösten Ausnahmen in Spring DataAccessExceptions umwandelt .

Wir können eine Komponente erstellen, die diese Funktionalität bereitstellt, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen JOOQToSpringExceptionTransformer Klasse, die den DefaultExecuteListener erweitert Klasse. Der DefaultExecuteListener Klasse ist die öffentliche Standardimplementierung des ExecuteListener Schnittstelle, die Listener-Methoden für verschiedene Lebenszyklusereignisse einer einzelnen Abfrageausführung bereitstellt.
  2. Überschreiben Sie die Ausnahme (ExecuteContext ctx) Methode des DefaultExecuteListener Klasse. Diese Methode wird aufgerufen, wenn zu irgendeinem Zeitpunkt des Ausführungslebenszyklus eine Ausnahme ausgelöst wird. Implementieren Sie diese Methode, indem Sie die folgenden Schritte ausführen:
    1. Holen Sie sich einen SQLDialekt Objekt aus der jOOQ-Konfiguration.
    2. Erstellen Sie ein Objekt, das den SQLExceptionTranslator implementiert Schnittstelle, indem Sie diese Regeln befolgen:
      1. Wenn der konfigurierte SQL-Dialekt gefunden wird, erstellen Sie einen neuen SQLErrorCodeSQLExceptionTranslator -Objekt und übergeben Sie den Namen des SQL-Dialekts als Konstruktorargument. Diese Klasse "wählt" die richtige DataAccessException aus durch Analyse herstellerspezifischer Fehlercodes.
      2. Wenn der SQL-Dialekt nicht gefunden wird, erstellen Sie einen neuen SQLStateSQLExceptionTranslator Objekt. Diese Klasse "wählt" die richtige DataAccessException aus durch Analysieren des in der SQLException gespeicherten SQL-Status .
    3. Erstellen Sie die DataAccessException -Objekt mithilfe des erstellten SQLExceptionTranslator-Objekts.
    4. Übergeben Sie die ausgelöste DataAccessException weiter zum ExecuteContext Objekt als Methodenargument angegeben.

Der Quellcode des JOOQToSpringExceptionTransformer Klasse sieht wie folgt aus:

import org.jooq.ExecuteContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DefaultExecuteListener;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;


public class JOOQToSpringExceptionTransformer extends DefaultExecuteListener {

    @Override
    public void exception(ExecuteContext ctx) {
        SQLDialect dialect = ctx.configuration().dialect();
        SQLExceptionTranslator translator = (dialect != null)
                ? new SQLErrorCodeSQLExceptionTranslator(dialect.name())
                : new SQLStateSQLExceptionTranslator();

        ctx.exception(translator.translate("jOOQ", ctx.sql(), ctx.sqlException()));
    }
}

Unsere Arbeit ist noch nicht getan. Lassen Sie uns alle Teile zusammenfügen und unsere Arbeit beenden, indem wir den Anwendungskontext unserer Beispielanwendung konfigurieren.

Konfigurieren des Anwendungskontexts

Dieser Abschnitt erklärt, wie wir den Anwendungskontext unserer Anwendung mithilfe der Java-Konfiguration konfigurieren können.

Beginnen wir damit, eine Eigenschaftendatei zu erstellen, die die Konfiguration unserer Beispielanwendung enthält.

Erstellen der Eigenschaftendatei

Wir können die Eigenschaftendatei erstellen, indem wir diesen Schritten folgen:

  1. Konfigurieren Sie die Datenbankverbindung. Wir müssen die JDBC-Treiberklasse, die JDBC-URL, den Benutzernamen des Datenbankbenutzers und das Passwort des Datenbankbenutzers konfigurieren.
  2. Konfigurieren Sie den Namen des verwendeten SQL-Dialekts.
  3. Konfigurieren Sie den Namen des SQL-Skripts, das die Datenbank unserer Beispielanwendung erstellt (dies ist ein optionaler Schritt, der nicht erforderlich ist, wenn Ihre Anwendung keine eingebettete Datenbank verwendet).

Die application.properties Datei sieht wie folgt aus:

#Database Configuration
db.driver=org.h2.Driver
db.url=jdbc:h2:target/jooq-example
db.username=sa
db.password=

#jOOQ Configuration
jooq.sql.dialect=H2

#DB Schema
db.schema.script=schema.sql

Lassen Sie uns weitermachen und herausfinden, wie wir den Anwendungskontext unserer Anwendung mithilfe der Java-Konfiguration konfigurieren können.

Erstellen der Konfigurationsklasse

Wir können den Anwendungskontext unserer Anwendung konfigurieren, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen Persistenzkontext Klasse.
  2. Stellen Sie sicher, dass die erstellte Klasse als Konfigurationsklasse erkannt wird, indem Sie die Klasse mit @Configuration kommentieren Anmerkung.
  3. Stellen Sie sicher, dass die jOOQ-Repositorys unserer Anwendung während des Komponenten-Scans gefunden werden. Wir können dies tun, indem wir die Konfigurationsklasse mit @ComponentScan kommentieren Anmerkung.
  4. Aktivieren Sie die annotationsgesteuerte Transaktionsverwaltung, indem Sie die Konfigurationsklasse mit @EnableTransactionManagement annotieren Anmerkung.
  5. Stellen Sie sicher, dass die Konfiguration unserer Anwendung aus der application.properties geladen wird Datei, die aus dem Klassenpfad gefunden wird. Wir können dies tun, indem wir die Konfigurationsklasse mit @PropertySource annotieren Anmerkung.
  6. Fügen Sie eine Umgebung hinzu Feld zur Konfigurationsklasse und kommentieren Sie das Feld mit @Autowired Anmerkung. Wir verwenden die Umgebung -Objekt, um die Eigenschaftswerte der Konfigurationseigenschaften abzurufen, die aus application.properties geladen werden Datei.
  7. Konfigurieren Sie die Datenquelle Bohne. Da unsere Anwendung BoneCP verwendet, haben wir eine neue BoneCPDataSource erstellt -Objekt und verwenden Sie es als unsere Datenquelle Bohne.
  8. Konfigurieren Sie den LazyConnectionDataSourceProxy Bohne. Diese Bean stellt sicher, dass die Datenbankverbindung träge abgerufen wird (d. h. wenn die erste Anweisung erstellt wird).
  9. Konfigurieren Sie den TransactionAwareDataSourceProxy Bohne. Diese Bean stellt sicher, dass alle JDBC-Verbindungen von Spring verwaltete Transaktionen kennen. Mit anderen Worten, JDBC-Verbindungen nehmen an Thread-gebundenen Transaktionen teil.
  10. Konfigurieren Sie den DataSourceTransactionManager Bohne. Wir müssen den LazyConnectionDataSourceProxy übergeben Bean als Konstruktorargument, wenn wir einen neuen DataSourceTransactionManager erstellen Objekt .
  11. Konfigurieren Sie den DataSourceConnectionProvider Bohne. jOOQ holt sich die verwendeten Verbindungen aus der DataSource als Konstruktorargument angegeben. Wir müssen den TransactionAwareDataSourceProxy übergeben Bean als Konstruktorargument, wenn wir einen neuen DataSourceConnectionProvider erstellen Objekt . Dadurch wird sichergestellt, dass die von jOOQ erstellten Abfragen an von Spring verwalteten Transaktionen teilnehmen.
  12. Konfigurieren Sie den JOOQToSpringExceptionTransformer Bohne.
  13. Konfigurieren Sie die Standardkonfiguration Bohne. Diese Klasse ist die Standardimplementierung der Konfiguration Schnittstelle, und wir können es verwenden, um jOOQ zu konfigurieren. Wir müssen drei Dinge konfigurieren:
    1. Wir müssen den ConnectionProvider setzen die verwendet wird, um Datenbankverbindungen zu erhalten und freizugeben.
    2. Wir müssen die benutzerdefinierten Ausführungslistener konfigurieren. Mit anderen Worten, wir müssen JOOQToSpringExceptionTransformer hinzufügen Bean zur erstellten DefaultConfiguration Objekt. Dadurch wird sichergestellt, dass die von jOOQ ausgelösten Ausnahmen in Spring DataAccessExceptions umgewandelt werden .
    3. Wir müssen den verwendeten SQL-Dialekt konfigurieren.
  14. Konfigurieren Sie den DefaultDSLContext Bohne. Wir verwenden diese Bean, wenn wir Datenbankabfragen mit jOOQ erstellen.
  15. Konfigurieren Sie den DataSourceInitializer Bohne. Wir verwenden diese Bean, um das Datenbankschema der H2-Datenbank zu erstellen, wenn unsere Anwendung gestartet wird (Wenn Sie keine eingebettete Datenbank verwenden, müssen Sie diese Bean nicht konfigurieren).

Der Quellcode des PersistenceContext Klasse sieht wie folgt aus:

import com.jolbox.bonecp.BoneCPDataSource;
import org.jooq.SQLDialect;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@ComponentScan({"net.petrikainulainen.spring.jooq.todo"})
@EnableTransactionManagement
@PropertySource("classpath:application.properties")
public class PersistenceContext {

    @Autowired
    private Environment env;

    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        BoneCPDataSource dataSource = new BoneCPDataSource();

        dataSource.setDriverClass(env.getRequiredProperty("db.driver"));
        dataSource.setJdbcUrl(env.getRequiredProperty("db.url"));
        dataSource.setUsername(env.getRequiredProperty("db.username"));
        dataSource.setPassword(env.getRequiredProperty("db.password"));

        return dataSource;
    }

    @Bean
    public LazyConnectionDataSourceProxy lazyConnectionDataSource() {
        return new LazyConnectionDataSourceProxy(dataSource());
    }

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

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

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

    @Bean
    public JOOQToSpringExceptionTransformer jooqToSpringExceptionTransformer() {
        return new JOOQToSpringExceptionTransformer();
    }

    @Bean
    public DefaultConfiguration configuration() {
        DefaultConfiguration jooqConfiguration = new DefaultConfiguration();

        jooqConfiguration.set(connectionProvider());
        jooqConfiguration.set(new DefaultExecuteListenerProvider(
            jooqToSpringExceptionTransformer()
        ));

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

        return jooqConfiguration;
    }

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

    @Bean
    public DataSourceInitializer dataSourceInitializer() {
        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource());

        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(
                new ClassPathResource(env.getRequiredProperty("db.schema.script"))
        );

        initializer.setDatabasePopulator(populator);
        return initializer;
    }
}

Woher wissen wir, dass diese Konfiguration funktioniert? Das ist eine gute Frage. Darüber sprechen wir im folgenden Abschnitt.

Funktioniert das wirklich?

Als ich anfing zu untersuchen, wie ich sicherstellen kann, dass die mit jOOQ erstellten Datenbankabfragen an von Spring verwalteten Transaktionen teilnehmen, bemerkte ich, dass es kein einfaches Problem ist, das zu lösen ist.

Die Beispielanwendung dieses Blogbeitrags enthält einige Integrationstests, die sicherstellen, dass Transaktionen (Commit und Rollback) in einem sehr einfachen Szenario funktionieren. Es gibt jedoch zwei Dinge, die wir berücksichtigen müssen, wenn wir die in diesem Blogbeitrag beschriebene Lösung verwenden:

1. Alle mit jOOQ erstellten Datenbankabfragen müssen innerhalb einer Transaktion ausgeführt werden.

Das Javadoc des TransactionAwareDataSourceProxy Klassenzustände:

Delegiert an DataSourceUtils für die automatische Teilnahme an Thread-gebundenen Transaktionen, die beispielsweise von DataSourceTransactionManager verwaltet werden . getConnection Aufrufe und Close-Calls bei zurückgegebenen Verbindungen wird sich innerhalb einer Transaktion richtig verhalten, d. h. immer auf der transaktionalen Verbindung arbeiten. Wenn nicht innerhalb einer Transaktion, normale DataSource Verhalten gilt.

Mit anderen Worten, wenn Sie mehrere komplexe Operationen ohne Transaktion durchführen, verwendet jOOQ für jede Operation eine andere Verbindung. Dies kann zu Race-Condition-Bugs führen.

2. Verwenden von TransactionAwareDataSourceProxy wird von seinem Javadoc nicht empfohlen.

Das Javadoc des TransactionAwareDataSourceProxy class hat einen Abschnitt, der so aussieht:

Dieser Proxy ermöglicht es dem Datenzugriffscode, mit der einfachen JDBC-API zu arbeiten und dennoch an von Spring verwalteten Transaktionen teilzunehmen, ähnlich wie JDBC-Code in einer J2EE/JTA-Umgebung. Verwenden Sie jedoch nach Möglichkeit die DataSourceUtils von Spring , JdbcTemplate oder JDBC-Operationsobjekte, um eine Transaktionsteilnahme auch ohne einen Proxy für die Ziel-DataSource zu erhalten , wodurch vermieden wird, dass ein solcher Proxy überhaupt erst definiert werden muss.

Das ist ein ziemlich vage Kommentar, weil es keine Erklärung bietet, warum wir es nicht verwenden sollten. Adam Zell schlug vor, dass die Verwendung von Reflektion zu Leistungsproblemen führen könnte, da die Klasse Reflektion verwendet.

Zusammenfassung

Wir haben den Anwendungskontext unserer Beispielanwendung nun erfolgreich konfiguriert. Dieses Tutorial hat vier Dinge gelehrt:

  • Wir haben gelernt, wie wir die erforderlichen Abhängigkeiten mit Maven erhalten können.
  • Wir haben gelernt, wie wir die von jOOQ ausgelösten Ausnahmen in Spring DataAccessExceptions umwandeln können .
  • Wir haben gelernt, wie wir den Anwendungskontext einer Anwendung konfigurieren können, die jOOQ und Spring verwendet.
  • Wir haben einen kurzen Blick auf die Dinge geworfen, die wir berücksichtigen müssen, wenn wir den in diesem Blogbeitrag beschriebenen Ansatz verwenden.

Der nächste Teil dieses Tutorials beschreibt, wie wir die Codegenerierungsunterstützung von jOOQ verwenden können.

P.S. Sie können die Beispielanwendung dieses Blogbeitrags von Github herunterladen.


Java-Tag