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をしているわけではなく、駆動表から取得して結合する表を取得するパターンのようだ…