1概要

この記事では、Java Object Oriented Querying –

jOOQ

– とSpring Frameworkと共同でそれを設定する簡単な方法を紹介します。

ほとんどのJavaアプリケーションはある種のSQL永続性を持ち、JPAなどのより高度なツールを使用してその層にアクセスします。それは便利ですが、場合によっては、データを取得したり、基盤となるDBが提供するすべての機能を実際に利用したりするために、より洗練された、微妙なツールが本当に必要です。

jOOQは、いくつかの典型的なORMパターンを避け、タイプセーフなクエリを構築し、クリーンで強力な流暢なAPIを介して生成されたSQLを完全に制御することを可能にするコードを生成します。


2 Mavenの依存関係

このチュートリアルのコードを実行するには、次の依存関係が必要です。


2.1. jOOQ

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


2.2. 春

この例に必要なSpringの依存関係はいくつかあります。しかし、物事を単純にするために、POMファイルにそれらの2つを明示的に含める必要があります。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.2.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.2.5.RELEASE</version>
</dependency>


2.3. データベース

例をわかりやすくするために、H2組み込みデータベースを使用します。

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


3コード生成


3.1. データベース構造

この記事を通して、これから扱うデータベースの構造を紹介しましょう。出版社が管理する本や著者に関する情報を格納するためのデータベースを作成する必要があるとします。ここで、著者は多くの本を書くことができ、本は多くの著者が共同執筆することができます。

それを簡単にするために、本のための

book

、作者のための

author

、そして作者と本の間の多対多の関係を表すための

author

book

と呼ばれる別のテーブルの3つのテーブルだけを生成します。

author

テーブルには、

id



first

name

、および

last

nameの3つの列があります。

book

テーブルには、

title

列と

id

主キーのみが含まれています。


intro

schema.sql__リソースファイルに格納されている次のSQLクエリは、必要なテーブルを作成し、それらにサンプルデータを移入するために、前にセットアップしたデータベースに対して実行されます。

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. プロパティMavenプラグイン

jOOQコードを生成するために3つの異なるMavenプラグインを使用します。これらのうちの最初のものは、Properties Mavenプラグインです。

このプラグインはリソースファイルから設定データを読み込むために使用されます。データはPOMに直接追加される可能性があるので必須ではありませんが、プロパティを外部で管理することをお勧めします。

このセクションでは、JDBCドライバークラス、データベースURL、ユーザー名、およびパスワードを含むデータベース接続のプロパティを

intro

config.properties__というファイルに定義します。これらのプロパティを外部化すると、データベースの切り替えや設定データの変更が簡単になります。

このプラグインの

read-project-properties

の目標は、設定データが他のプラグインによる使用のために準備されることができるように初期段階に縛られるべきです。この場合、

initialize

フェーズにバインドされています。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>properties-maven-plugin</artifactId>
    <version>1.0.0</version>
    <executions>
        <execution>
            <phase>initialize</phase>
            <goals>
                <goal>read-project-properties</goal>
            </goals>
            <configuration>
                <files>
                    <file>src/main/resources/intro__config.properties</file>
                </files>
            </configuration>
        </execution>
    </executions>
</plugin>


3.3. SQL Mavenプラグイン

SQL Mavenプラグインは、データベーステーブルを作成してデータを設定するためのSQLステートメントを実行するために使用されます。これは、Properties Mavenプラグインによって

intro

config.properties

ファイルから抽出されたプロパティーを利用し、

intro

schema.sql

リソースからSQLステートメントを取得します。

SQL Mavenプラグインは以下のように構成されています。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>sql-maven-plugin</artifactId>
    <version>1.5</version>
    <executions>
        <execution>
            <phase>initialize</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
                <driver>${db.driver}</driver>
                <url>${db.url}</url>
                <username>${db.username}</username>
                <password>${db.password}</password>
                <srcFiles>
                    <srcFile>src/main/resources/intro__schema.sql</srcFile>
                </srcFiles>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.191</version>
        </dependency>
    </dependencies>
</plugin>

このプラグインはPOMファイルのProperties Mavenプラグインよりも後に配置する必要があります。実行目標はどちらも同じフェーズにバインドされており、Mavenはそれらをリストされている順序で実行するためです。


3.4. jOOQ Codegenプラグイン

jOOQ Codegenプラグインはデータベースのテーブル構造からJavaコードを生成します。正しい実行順序を保証するために、その「生成」目標を「生成元」フェーズにバインドする必要があります。プラグインメタデータは次のようになります。

<plugin>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-codegen-maven</artifactId>
    <version>${org.jooq.version}</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <jdbc>
                    <driver>${db.driver}</driver>
                    <url>${db.url}</url>
                    <user>${db.username}</user>
                    <password>${db.password}</password>
                </jdbc>
                <generator>
                    <target>
                        <packageName>com.baeldung.jooq.introduction.db</packageName>
                        <directory>src/main/java</directory>
                    </target>
                </generator>
            </configuration>
        </execution>
    </executions>
</plugin>


3.5. コードを生成する

ソースコード生成のプロセスを完了するには、Maven

generate-sources

フェーズを実行する必要があります。 Eclipseでは、プロジェクトを右クリックして

Run As

– >

Maven generate-sources

を選択することでこれを実行できます。コマンドが完了すると、

author



book



author

book__テーブル(およびその他のサポートクラス用のテーブル)に対応するソースファイルが生成されます。

jOOQが生み出したものを見るためにテーブルクラスを掘り下げましょう。各クラスには、名前と同じ名前の静的フィールドがありますが、名前に含まれるすべての文字は大文字になります。以下は、生成されたクラスの定義から抜粋したコードスニペットです。


Author

クラス:

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

   //other class members
}


Book

クラス:

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

   //other class members
}


AuthorBook

クラス:

public class AuthorBook extends TableImpl<AuthorBookRecord> {
    public static final AuthorBook AUTHOR__BOOK = new AuthorBook();

   //other class members
}

これらの静的フィールドによって参照されるインスタンスは、プロジェクト内の他のレイヤーを扱うときに対応するテーブルを表すためのデータアクセスオブジェクトとして機能します。


4スプリング構成


4.1. jOOQ例外をSpring

に翻訳する

jOOQの実行からスローされる例外をSpringのデータベースアクセスのサポートと一致させるために、それらを

DataAccessException

クラスのサブタイプに変換する必要があります。

例外を変換するための

ExecuteListener

インターフェースの実装を定義しましょう。

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

このクラスはSpringアプリケーションのコンテキストで使用されます。

** 4.2. Springの設定

このセクションでは、Springアプリケーションコンテキストで使用されるメタデータとBeanを含む

PersistenceContext

を定義するための手順を説明します。

必要な注釈をクラスに適用することから始めましょう。


  • @ Configuration

    :クラスをそのコンテナとして認識させる


**

@ ComponentScan

:

value

を含むスキャンディレクティブを設定する

コンポーネントを検索するためのパッケージ名の配列を宣言するオプション。に
このチュートリアルでは、検索対象のパッケージは、
jOOQ Codegen Mavenプラグイン
**

@ EnableTransactionManagement

:トランザクションの管理を有効にします


**

@ PropertySource

:プロパティファイルの場所を指定します

ロードされます。この記事の値は、構成データとデータベースの方言を含むファイルを指しています。これは、サブセクション4.1で述べたのと同じファイルです。

@Configuration
@ComponentScan({"com.baeldung.jooq.introduction.db.public__.tables"})
@EnableTransactionManagement
@PropertySource("classpath:intro__config.properties")
public class PersistenceContext {
   //Other declarations
}

次に、

Environment

オブジェクトを使用して構成データを取得し、これを使用して

DataSource

Beanを構成します。

@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;
}

データベースアクセス操作を処理するためにいくつかのBeanを定義します。

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

最後に、jOOQ

Configuration

実装を提供し、それを

DSLContext

クラスで使用されるSpring Beanとして宣言します。

@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 Spring

でjOOQを使う

このセクションでは、一般的なデータベースアクセスクエリでのjOOQの使い方について説明します。データの挿入、更新、削除を含む「書き込み」操作の種類ごとに、コミットとロールバックの2つのテストがあります。 「書き込み」クエリを検証するためにデータを選択するときの「読み取り」操作の使用方法が示されています。

すべてのテストメソッドで使用されるように、自動配線された

DSLContext

オブジェクトとjOOQ生成クラスのインスタンスを宣言することから始めます。

@Autowired
private DSLContext dsl;

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


5.1. データを挿入する

最初のステップは、テーブルにデータを挿入することです。

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

データを抽出するための

SELECT

クエリ

Result<Record3<Integer, String, Integer>> 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();

上記のクエリは次のような出力を生成します。

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

結果は

Assert

APIによって確認されます。

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

無効なクエリが原因で失敗が発生すると、例外がスローされ、トランザクションはロールバックします。次の例では、

INSERT

クエリが外部キー制約に違反しているため、例外が発生します。

@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. データを更新する

それでは、既存のデータを更新しましょう。

dsl.update(author)
  .set(author.LAST__NAME, "Baeldung")
  .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();

必要なデータを入手してください。

Result<Record3<Integer, String, String>> 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();

出力は次のようになります。

+----+---------+----------------------------------+
|  ID|LAST__NAME|TITLE                             |
+----+---------+----------------------------------+
|   3|Baeldung |Building your REST API with Spring|
+----+---------+----------------------------------+

次のテストでは、jOOQが期待通りに動作したことを確認します。

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

失敗した場合は、例外がスローされトランザクションがロールバックします。これをテストで確認します。

@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. データを削除する

次のメソッドはいくつかのデータを削除します。

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

影響を受けるテーブルを読み取るためのクエリは次のとおりです。

Result<Record3<Integer, String, String>> result = dsl
  .select(author.ID, author.FIRST__NAME, author.LAST__NAME)
  .from(author)
  .fetch();

クエリ出力:

+----+----------+---------+
|  ID|FIRST__NAME|LAST__NAME|
+----+----------+---------+
|   3|Bryan     |Basham   |
+----+----------+---------+

次のテストは削除を確認します。

assertEquals(1, result.size());
assertEquals("Bryan", result.getValue(0, author.FIRST__NAME));
assertEquals("Basham", result.getValue(0, author.LAST__NAME));

一方、クエリが無効な場合は、例外がスローされ、トランザクションはロールバックします。次のテストはそれを証明します。

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


6. 結論

このチュートリアルでは、データベースを扱うためのJavaライブラリであるjOOQの基本を紹介しました。データベース構造からソースコードを生成する手順と、新しく作成されたクラスを使用してそのデータベースと対話する方法について説明しました。

これらすべての例とコードスニペットの実装はhttps://github.com/eugenp/tutorials/tree/master/spring-jooq[GitHubプロジェクト]にあります。