技術メモ

技術メモ

ラフなメモ

JavaO/RマッパーであるDomaを試してみる

Doma の Tutorial をやってみる

公式のチュートリアルが丁寧なので、それを見るのがよいです。

http://doma.seasar.org/setup.html

概要

重要っぽいところだけ抜粋(主に利用するユーザとしても視点で)

  • O/R マッパー*1
  • Dao パターンを使っている
  • 命名規則
    • ファイル名での命名規則はなく、アノテーションを用いる。例えば UPDATE を発行する SQL であれば、 @Update と注釈することになる
      • プログラムの挙動をわかりやすくするため
  • JavaSQL の分離
    • 保守性が悪くなるため、Java のコードに SQL は記載しない。SQL は全て外部ファイルに記載する。
  • Dao メソッドの引数と SQL のバインドパラメータのマッピング

SQL ファイルの作り方とメソッドの実装方法がポイントな気がします。

実際にやってみた

ポイントだけメモします。

注釈処理(Annotation Processing)の設定

有効にして、ファクトリーパスに doma01.38.0.jar を追加します。デフォルトだとパッケージ・エクスプローラに生成される Java ファイルが表示されないのでフィルターを変更しておきます。

フォルダ構成

フォルダ 説明
src/main/java 設定クラス、Daoインタフェース、エンティティクラス等のソースフォルダ
src/main/resources QLファイル等のソースフォルダです。 SQLファイルは、Domaの規約に則ってMETA-INFフォルダ以下に配置されます。 また、JDBCドライバのプロバイダ構成ファイルがMETA-INF/servicesフォルダ以下にjava.sql.Driverという名前で配置されます。
src/test/java Daoを利用するコードのためのソースフォルダです。 Daoを利用するコードはすべてJUnitのテストクラスとして記述されています。
src/test/resources 使用していません。
.apt_generated apt(Annotation Processing Tool)によって生成されたコードを格納するソースフォルダです。

pom はこんな感じ

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>sample</groupId>
    <artifactId>sample</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/postgresql/postgresql -->
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.1-901.jdbc4</version>
        </dependency>
    </dependencies>
</project>

MavenDoma の artifactId を pom に記載したところ、Missing artifact org.seasar.doma:doma-tool:jar:0.9.1 となったので手動で jar を追加しました。

<!-- https://mvnrepository.com/artifact/org.seasar.doma/doma-tool -->
<dependency>
    <groupId>org.seasar.doma</groupId>
    <artifactId>doma-tool</artifactId>
    <version>0.9.1</version>
</dependency>

データベースは H2 ではなく PostgreSQL を使いました

docker run --name my-db -p 5432:5432 -e POSTGRES_USER=dev -e POSTGRES_PASSWORD=dev -d postgres:9.6

インスタンス化する Dialect を切り替えて、 createDataSource() の設定を修正するだけです。

public class AppConfig extends DomaAbstractConfig {

    protected static final LocalTransactionalDataSource dataSource = createDataSource();

    protected static final Dialect dialect = new PostgresDialect();

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

    protected static LocalTransactionalDataSource createDataSource() {
        SimpleDataSource dataSource = new SimpleDataSource();
        dataSource.setUrl("jdbc:postgresql:dev://localhost:5432");
        dataSource.setUser("dev");
        dataSource.setPassword("dev");
        return new LocalTransactionalDataSource(dataSource);
    }

    public static LocalTransaction getLocalTransaction() {
        return dataSource.getLocalTransaction(defaultJdbcLogger);
    }
}

実行

Main.java を実行すると以下のように出力されました。

情報: [DOMA2063] ローカルトランザクション[233530418]を開始しました。 [土 3 23 18:15:24 JST 2019]
情報: ENTRY [土 3 23 18:15:24 JST 2019]
情報: [DOMA2076] SQLログ : SQLファイル=[META-INF/example/EmployeeDao/selectById.sql],
select * from EMPLOYEE where EMPLOYEE_ID = 3 [土 3 23 18:15:24 JST 2019]
情報: RETURN example.Employee@27f674d [土 3 23 18:15:24 JST 2019]
情報: ENTRY [土 3 23 18:15:24 JST 2019]
情報: [DOMA2076] SQLログ : SQLファイル=[null],
update EMPLOYEE set EMPLOYEE_NAME = 'HOMU', HIREDATE = '1981-02-22', SALARY = 4550.00, VERSION_NO = 3 + 1 where EMPLOYEE_ID = 3 and VERSION_NO = 3 [土 3 23 18:15:24 JST 2019]
情報: RETURN 1 [土 3 23 18:15:24 JST 2019]
情報: [DOMA2067] ローカルトランザクション[233530418]をコミットしました。 [土 3 23 18:15:24 JST 2019]
情報: [DOMA2064] ローカルトランザクション[233530418]を終了しました。 [土 3 23 18:15:24 JST 2019]

チュートリアルでの主要なクラス

設定クラス

エンティティクラス

  • src/main/java/tutorial/entity/Employee.java

テーブルやSQLの結果セットにマッピングされたクラスで @Entity を注釈して示します。テーブルの主キーには @Id を注釈します。識別子を自動生成する場合は @GeneratedValue を用いることができます。

@Entity(listener = EmployeeListener.class)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(sequence = "EMPLOYEE_SEQ")
    Integer id;

    String name;

    int age;

    Salary salary;

    @Column(name = "JOB_TYPE")
    JobType jobType;

    Date hiredate;

    @Column(name = "DEPARTMENT_ID")
    Integer departmentId;

    @Version
    @Column(name = "VERSION")
    Integer version;

    Timestamp insertTimestamp;

    Timestamp updateTimestamp;

    @OriginalStates
    Employee originalStates;

    ...
}

エンティティリスナークラス

エンティティリスナークラスのインスタンスは、エンティティがデータベースに挿入、更新、削除される前後に呼び出されます。

ドメインクラス

Dao インターフェース

  • src/main/java/tutorial/dao/EmployeeDao.java

Dao インターフェースは @Dao を用いて示します。 @Dao(config = ) には設定クラスである AppConfig クラスを指定します。すべてのメソッドには @Select@Update などのクエリの種別を示すアノテーションが必要です。

@Dao(config = AppConfig.class)
public interface EmployeeDao {

    @Select
    Employee selectById(Integer id);

    @Select
    List<Employee> selectByAgeRange(Integer min, Integer max);

    @Select
    List<Employee> selectByAges(List<Integer> ages);

    @Select
    List<Employee> selectByNames(List<String> names);

    ...

    @Insert
    int insert(Employee employee);

    @Insert(sqlFile = true)
    int insertWithSqlFile(Employee employee);

    @Update
    int update(Employee employee);

    @Update(sqlFile = true)
    int updateWithSqlFile(Employee employee);

    @Delete
    int delete(Employee employee);

    @Delete(sqlFile = true)
    int deleteWithSqlFile(Employee employee);

    ...

}

SQL ファイル

src/main/resources/META-INF/tutorial/EmployeeDao 配下に配置されます。 フォルダの名前は Dao インタフェースと対応づけられます。SQL ファイルは、名前から拡張子を除いたものが Dao インタフェースのメソッドに一致します。たとえば、selectById.sql は Dao インタフェースの selectById メソッドに対応します。 selectById.sql の中身は次のようなテキストです。

select * from employee where id = /* id */0

所感

「暗黙的な規約よりも明示的な設定を重視する」というコンセプトのとおり、アノテーションを使って明示的に RDB にアクセスするようなフレームワークに感じた。 フレームワークのお作法に則って開発すればシンプルな実装で RDB へのアクセスができそうな気がしている。

*1:O/Rマッピングとは、「オブジェクト」と「リレーショナルデータベース」をマッピング(対応付け)することで、O/R マッパーがO/Rマッピングの機能を持つ