Stream APIを使ったORM。
概要
- MavenプラグインでDBスキーマからモデルのクラス/レポジトリー(に該当する)クラスを吐き出す
- Applicatoinクラスからレポジトリー(に該当する)クラスを取り出す
- レポジトリーにStream APIを通じてクエリーを投げる
DBスキーマからモデルを出力するツールはgradleに対応していないのでmavenを用いる(まあ、gradle用にツールを作ってもよいのだが…)。なお、ツールで作られるコードは src/main/generated
に出力する。
pluginの方
<build>
<plugins>
<plugin>
<groupId>com.speedment</groupId>
<artifactId>speedment-maven-plugin</artifactId>
<version>3.0.3</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>src/main/generated</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
dependenciesの方
<dependencies>
<dependency>
<groupId>com.speedment</groupId>
<artifactId>runtime</artifactId>
<version>3.0.3</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
</dependencies>
この記事におけるDBのスキーマは次のとおり
CREATE TABLE student
(
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
last_name VARCHAR(10) NOT NULL,
first_name VARCHAR(10) NOT NULL,
admission_year INT NOT NULL
)
ENGINE = InnoDB CHARACTER SET utf8mb4;
CREATE TABLE book_categories
(
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(20) NOT NULL
)
ENGINE = InnoDB CHARACTER SET utf8mb4;
CREATE UNIQUE INDEX book_categories_name_uindex ON book_categories (name);
CREATE TABLE books
(
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
title VARCHAR(60) NOT NULL,
category INT NOT NULL,
CONSTRAINT books_book_categories_id_fk FOREIGN KEY (category) REFERENCES book_categories (id) ON UPDATE CASCADE
)
ENGINE = InnoDB CHARACTER SET utf8mb4;
CREATE INDEX books_title_index ON books (title);
CREATE TABLE book_lending
(
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
student_id BIGINT NOT NULL,
book_id BIGINT NOT NULL,
rent_date DATE NOT NULL,
return_date DATE NOT NULL,
CONSTRAINT book_lending_student_id_fk FOREIGN KEY (student_id) REFERENCES student (id) ON UPDATE CASCADE,
CONSTRAINT book_lending_books_id_fk FOREIGN KEY (book_id) REFERENCES books (id)
)ENGINE = InnoDB CHARACTER SET utf8mb4;
CREATE TABLE book_back
(
id BIGINT PRIMARY KEY NOT NULL,
return_date DATE NOT NULL,
CONSTRAINT book_back_book_lending_id_fk FOREIGN KEY (id) REFERENCES book_lending (id)
)ENGINE = InnoDB CHARACTER SET utf8mb4;
また、サンプル用に次のデータを使う
INSERT INTO book_categories (id, name) VALUES
(1, '文学'), (2, '数学'), (3, '物理');
INSERT INTO books (id, title, category) VALUES
( 1, '異邦人', 1) # 1
, ( 2, 'モンテ・クリスト伯', 1) # 2
, ( 3, '線形代数入門', 2) # 3
, ( 4, '解析入門', 2) # 4
, ( 5, 'δ-ε法', 2) # 5
, ( 6, 'フーリエ解析', 2) # 6
, ( 7, '統計学入門', 2) # 7
, ( 8, '力学', 3) # 8
, ( 9, '熱力学', 3) # 9
, (10, '電磁気学', 3) # 10
, (11, '量子論', 3) # 11
;
INSERT INTO student (id, last_name, first_name, admission_year) VALUES
(1, '竹村', '央人', 2015) # 1
, (2, '土井', '正一', 2016) # 2
, (3, '高瀬', '彩世', 2017) # 3
, (4, '武田', '麻未', 2017) # 4
, (5, '松本', '麻郁', 2015) # 5
;
INSERT INTO book_lending (id, student_id, book_id, rent_date, return_date) VALUES
(1, 1, 4, '2017-01-13', '2017-01-20') # 1
, (2, 1, 5, '2017-01-13', '2017-01-20') # 2
, (3, 1, 6, '2017-01-13', '2017-01-20') # 3
, (4, 2, 10, '2017-01-15', '2017-01-22') # 4
, (5, 3, 1, '2017-01-16', '2017-01-23') # 5
, (6, 5, 3, '2017-01-17', '2017-01-24') # 6
, (7, 5, 8, '2017-01-17', '2017-01-24') # 7
, (8, 1, 3, '2017-01-23', '2017-01-30') # 8
;
INSERT INTO book_back (id, return_date) VALUES
(1, '2017-01-18')
, (2, '2017-01-18')
, (3, '2017-01-18')
, (5, '2017-01-23')
, (6, '2017-01-22')
, (8, '2017-01-24')
;
クラスの出力
mavenに speedment:tool
というgoalが追加されているので実行するとJavaFX製のツールが起動するので、DBのスキーマを読み取るなどする。
最終的に次のようなクラスを出力される。
src/main/generated/com/example/book/books/Books.java
src/main/generated/com/example/book/books/BooksImpl.java
src/main/generated/com/example/book/books/BooksManager.java
src/main/generated/com/example/book/books/BooksManagerImpl.java
src/main/generated/com/example/book/books/BooksSqlAdapter.java
src/main/generated/com/example/book/books/generated/GeneratedBooks.java
src/main/generated/com/example/book/books/generated/GeneratedBooksImpl.java
src/main/generated/com/example/book/books/generated/GeneratedBooksManager.java
src/main/generated/com/example/book/books/generated/GeneratedBooksManagerImpl.java
src/main/generated/com/example/book/books/generated/GeneratedBooksSqlAdapter.java
...
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleApplication.java
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleApplicationBuilder.java
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleApplicationImpl.java
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleMetadata.java
src/main/generated/com/example/book/SpeedmentSampleApplication.java
src/main/generated/com/example/book/SpeedmentSampleApplicationBuilder.java
src/main/generated/com/example/book/SpeedmentSampleApplicationImpl.java
...
一つのテーブルにつき次のクラスが作成される
- テーブルモデルのインターフェース x 2
- テーブルモデルの実装クラス x 2
- Managerインターフェース(レポジトリー相当) x 2
- Manager実装クラス x 2
- SqlAdapterクラス x 2
それに加えて、4つのアプリケーションクラスも生成される
- Applicationインターフェース
- Application実装クラス
- ApplicationBuilderクラス
- Metadataクラス
また、スキーマ定義をjsonにして出力しているのでチームで共有する場合はこのjsonからクラスを生成できるように speedment:generate
というgoalもある。
利用方法
ApplicationBuilderクラスにDBの接続情報(これはjsonに書き出されているものも利用できる)、ログの設定を入力してApplicationクラスを取得する。
SpeedmentSampleApplication app = new SpeedmentSampleApplicationBuilder()
.withPassword("speedment")
.withLogging(ApplicationBuilder.LogType.STREAM)
.withLogging(ApplicationBuilder.LogType.REMOVE)
.withLogging(ApplicationBuilder.LogType.UPDATE)
.withLogging(ApplicationBuilder.LogType.PERSIST)
.build();
このApplicationクラスからManagerクラスを取得して、Stream APIを使ってデータにアクセスする
単純なクエリー
まず単純な例として student
テーブルから admission_year
が 2015
のレコードを取得する。
@Test
void simpleQuery() {
app.getOrThrow(StudentManager.class)
.stream()
.filter(Student.ADMISSION_YEAR.equal(2015))
.sorted(Student.ID.comparatorNullFieldsLast())
.collect(toList())
.forEach(System.out::println);
}
実行すると次のように表示される。
2017-03-05T14:52:17.839Z INFO [main] (#APPLICATION_BUILDER) -
____ _ _
/ ___'_ __ __ __ __| |_ __ __ __ _ __ | |
\___ | '_ |/ \/ \ / _ | '_ \ _ \ / \ '_ \| |_
___)| |_)| '_/ '_/| (_| | | | | | | '_/ | | | _|
|____| .__|\__\\__\ \____|_| |_| |_|\__\_| |_| '_
=======|_|======================================\__|==
:: Speedment by Speedment, Inc.:: (v3.0.3)
2017-03-05T14:52:17.862Z INFO [main] (#APPLICATION_BUILDER) - Speedment (Open Source) version 3.0.3 by Speedment, Inc. Specification version 3.0
2017-03-05T14:52:17.880Z INFO [main] (#APPLICATION_BUILDER) - Java Platform API Specification 1.8 by Oracle Corporation. Implementation Oracle Corporation 1.8.0_111 by Oracle Corporation
2017-03-05T14:52:18.571Z INFO [main] (#APPLICATION_BUILDER) - MySQL, 5.7.17, MySQL Connector Java mysql-connector-java-5.1.40 ( Revision: 402933ef52cad9aa82624e80acbea46e3a701ce6 ), JDBC version 4.0
2017-03-05T14:52:18.631Z DEBUG [main] (#STREAM) - SELECT `id`,`last_name`,`first_name`,`admission_year` FROM `speedment-sample`.`student` WHERE (`speedment-sample`.`student`.`admission_year` = ?), values:[2015]
StudentImpl { id = 1, lastName = 竹村, firstName = 央人, admissionYear = 2015 }
StudentImpl { id = 5, lastName = 松本, firstName = 麻郁, admissionYear = 2015 }
Stream APIにて filter
メソッドで呼び出された箇所が WHERE
句となってSQLが発行されるようだ。
1:Nのjoin
1:Nの関係にあるテーブルをJOINする場合は、 collect
メソッドにて Collectors#groupingBy
を用いる。
@Test
void joinQuery(){
final BooksManager bm=app.getOrThrow(BooksManager.class);
final BookCategoriesManager bcm=app.getOrThrow(BookCategoriesManager.class);
final Map<BookCategories, List<Books>>booksGroupingByCategory=bm.stream()
.filter(Books.CATEGORY.equal(2))
.collect(groupingBy(bcm.finderBy(Books.CATEGORY)));
booksGroupingByCategory.forEach((k,v) -> {
System.out.println(String.format("category: %s", k));
System.out.println(String.format("books: %s", v));
});
}
実行すると次のように表示される。
2017-03-05T15:01:28.117Z INFO [main] (#APPLICATION_BUILDER) -
____ _ _
/ ___'_ __ __ __ __| |_ __ __ __ _ __ | |
\___ | '_ |/ \/ \ / _ | '_ \ _ \ / \ '_ \| |_
___)| |_)| '_/ '_/| (_| | | | | | | '_/ | | | _|
|____| .__|\__\\__\ \____|_| |_| |_|\__\_| |_| '_
=======|_|======================================\__|==
:: Speedment by Speedment, Inc.:: (v3.0.3)
2017-03-05T15:01:28.130Z INFO [main] (#APPLICATION_BUILDER) - Speedment (Open Source) version 3.0.3 by Speedment, Inc. Specification version 3.0
2017-03-05T15:01:28.132Z INFO [main] (#APPLICATION_BUILDER) - Java Platform API Specification 1.8 by Oracle Corporation. Implementation Oracle Corporation 1.8.0_111 by Oracle Corporation
2017-03-05T15:01:28.963Z INFO [main] (#APPLICATION_BUILDER) - MySQL, 5.7.17, MySQL Connector Java mysql-connector-java-5.1.40 ( Revision: 402933ef52cad9aa82624e80acbea46e3a701ce6 ), JDBC version 4.0
2017-03-05T15:01:29.040Z DEBUG [main] (#STREAM) - SELECT `id`,`title`,`category` FROM `speedment-sample`.`books` WHERE (`speedment-sample`.`books`.`category` = ?), values:[2]
2017-03-05T15:01:29.107Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.146Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.153Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.159Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.167Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
category: BookCategoriesImpl { id = 2, name = 数学 }
books: [BooksImpl { id = 3, title = 線形代数入門, category = 2 }, BooksImpl { id = 4, title = 解析入門, category = 2 }, BooksImpl { id = 5, title = δ-ε法, category = 2 }, BooksImpl { id = 6, title = フーリエ解析, category = 2 }, BooksImpl { id = 7, title = 統計学入門, category = 2 }]
JOINの結果だが、残念なことにJOINをしているわけではなく、駆動表から取得して結合する表を取得するパターンのようだ…