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テーブルには、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プラグイン
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を選択することでこれを行うことができます。 コマンドが完了すると、author、book、author_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にあります。