mike-neckのブログ

Java or Groovy or Swift or Golang

Speedment ORMのメモ

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のスキーマは次のとおり

f:id:mike_neck:20170305230003p:plain

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

クラスの出力

mavenspeedment: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_year2015 のレコードを取得する。

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