SpringによるjOOQの紹介

SpringでのJooqの概要

1. 概要

この記事では、Jooqオブジェクト指向のクエリ(Jooq)と、Spring Frameworkとのコラボレーションで設定する簡単な方法を紹介します。

ほとんどのJavaアプリケーションには、何らかのSQL永続性があり、JPAなどの高レベルツールの助けを借りてその層にアクセスします。 これは便利ですが、場合によっては、データにアクセスしたり、基盤となるDBが提供するすべてのものを実際に利用したりするために、より細かく、より微妙なツールが必要になります。

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

2. Mavenの依存関係

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

2.1. jOOQ


    org.Jooq
    jooq
    3.7.3

2.2. 春

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


    org.springframework
    spring-context
    4.2.5.RELEASE


    org.springframework
    spring-jdbc
    4.2.5.RELEASE

2.3. データベース

この例で物事を簡単にするために、H2組み込みデータベースを使用します。


    com.h2database
    h2
    1.4.191

3. コード生成

3.1. データベース構造

この記事全体で使用するデータベース構造を紹介しましょう。 出版社が管理する本や著者の情報を保存するデータベースを作成する必要があるとします。著者は多くの本を書くことができ、本は多くの著者が共同で書くことができます。

簡単にするために、本のbook、著者のauthor、および著者との多対多の関係を表すauthor_bookという別のテーブルの3つのテーブルのみを生成します。本。 authorテーブルには、idfirst_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プラグイン

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

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

このセクションでは、intro_config.propertiesという名前のファイルで、JDBCドライバークラス、データベースURL、ユーザー名、パスワードなどのデータベース接続のプロパティを定義します。 これらのプロパティを外部化すると、データベースの切り替えや構成データの変更が簡単になります。

このプラグインのread-project-propertiesの目標は、構成データを他のプラグインで使用できるように準備できるように、初期段階にバインドする必要があります。 この場合、initializeフェーズにバインドされます。


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

3.3. SQLMavenプラグイン

SQL Mavenプラグインは、SQLステートメントを実行してデータベーステーブルを作成および設定するために使用されます。 これは、Properties Mavenプラグインによってintro_config.propertiesファイルから抽出されたプロパティを利用し、intro_schema.sqlリソースからSQLステートメントを取得します。

SQL Mavenプラグインは次のように構成されます。


    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
        
    

実行目標は両方とも同じフェーズにバインドされているため、このプラグインはPOMファイルのProperties Mavenプラグインよりも後に配置する必要があり、Mavenはリストされている順序で実行することに注意してください。

3.4. jOOQCodegenプラグイン

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


    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. コードを生成する

ソースコード生成のプロセスを完了するには、Mavengenerate-sourcesフェーズを実行する必要があります。 Eclipseでは、プロジェクトを右クリックしてRun As –>Maven generate-sourcesを選択することでこれを行うことができます。 コマンドが完了すると、authorbookauthor_bookテーブル(およびクラスをサポートするための他のいくつか)に対応するソースファイルが生成されます。

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

Authorクラス:

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

    // other class members
}

Bookクラス:

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

    // other class members
}

AuthorBookクラス:

public class AuthorBook extends TableImpl {
    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:クラスをBeanのコンテナとして認識されるようにします

  • @ComponentScan:コンポーネントを検索するためのパッケージ名の配列を宣言するvalueオプションを含む、スキャンディレクティブを構成します。 このチュートリアルでは、検索するパッケージはJooq Codegen Mavenプラグインによって生成されたパッケージです

  • @EnableTransactionManagement:トランザクションをSpringで管理できるようにします

  • @PropertySource:ロードするプロパティファイルの場所を示します。 この記事の値は、構成データとデータベースの方言を含むファイルを指します。これは、たまたまサブセクション4.1で言及したファイルと同じです。

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

次に、Environmentオブジェクトを使用して構成データを取得します。このデータは、DataSourceBeanの構成に使用されます。

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

最後に、JooqConfiguration実装を提供し、それをDSLContextクラスで使用されるSpringBeanとして宣言します。

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

結果はAssertAPIによって確認されます。

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

必要なデータを取得します。

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

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

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

次のテストでは、Jooqが期待どおりに機能したことを確認します。

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

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

@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> 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の基本を紹介しました。 データベース構造からソースコードを生成する手順と、新しく作成されたクラスを使用してそのデータベースと対話する方法について説明しました。

これらすべての例とコードスニペットの実装は、a GitHub projectにあります。