やっぱり RDBMS と連携したかったのでやってみました。
参考にしたのは
です。
build.gradle
plugins { ... id 'org.seasar.doma.compile' version '1.1.0' } dependencies { ... annotationProcessor "org.seasar.doma:doma-processor:2.37.0" implementation "org.seasar.doma:doma-core:2.37.0" implementation 'io.micronaut.flyway:micronaut-flyway' runtimeOnly 'io.micronaut.sql:micronaut-jdbc-hikari' runtimeOnly "com.h2database:h2:1.4.200" ... }
Doma2 はアノテーションプロセッサで interface から Dao の実装クラスを自動的に作ってくれます。
コネクションプールには HikariCP を使い、データベースのマイグレーションツールとして flyway を使います。 RDBMS は H2 にしましたが、この辺りは適宜変えてください。
LocalTransactionManager 関連
ここ に書いてあるように、Config の実装クラスを定義して、@Singleton
で登録します。
DomaConfigFactory
@Factory public class DomaConfigFactory { @Singleton public LocalTransactionDataSource localTransactionDataSource(DataSource dataSource) { return new LocalTransactionDataSource(dataSource); } @Singleton public LocalTransactionManager localTransactionManager(LocalTransactionDataSource dataSource) { return new LocalTransactionManager( dataSource.getLocalTransaction(ConfigSupport.defaultJdbcLogger)); } }
LocalTransactionManager を @Factory
経由で登録します。
DomaConfig
@Singleton @RequiredArgsConstructor public class DomaConfig implements Config { private final LocalTransactionDataSource dataSource; private final LocalTransactionManager transactionManager; // 1 @Override public DataSource getDataSource() { return dataSource; } @Override public Dialect getDialect() { return new H2Dialect(); // 2 } @Override public TransactionManager getTransactionManager() { return transactionManager; } }
- LocalTransactionManager は Factory で登録したインスタンスを Inject します
- プロパティからとるとか良い感じにしてください。今回は決め打ちにしました
Dao
@DaoConfig
@AnnotateWith(annotations = @Annotation(target = AnnotationTarget.CLASS, type = Singleton.class)) public @interface DaoConfig {}
TaskDao
@Dao @DaoConfig // 1 public interface TaskDao { @Insert Result<TaskEntity> insert(TaskEntity taskEntity); @Select @Sql("SELECT * FROM tasks ORDER BY task_id") List<TaskEntity> selectAll(); }
- Dao の interface に
@DaoConfig
を付与することで、アノテーションプロセッサで生成する impl クラスに@Singleton
を付与してくれます。
後は、いつものように Doma2 を使えば良いです。
接続情報
RDBMS と flyway 用の設定をします。
datasources: #1 default: url: 'jdbc:h2:mem:micronaut-sample;LOCK_TIMEOUT=10000;MODE=PostgreSQL' username: 'sa' password: '' driverClassName: 'org.h2.Driver' schema-generate: CREATE_DROP dialect: H2 flyway: datasources: default: locations: db/migration #2
- 接続情報を設定します。
schema-generate
はテストの時だけ設定します。 - flyway が読み込む sql ファイルの配置場所を指定します。
Dao のテスト
TaskDaoTest
@MicronautTest // 1 @Property(name = "flyway.datasources.default.locations", value = "db/migration,db_fixtures/minimum") // 2 class TaskDaoTest { @Inject private TaskDao sut; // 3 @Inject private LocalTransactionManager transactionManager; // 4 @Test void testInsert() { transactionManager.required( // 5 () -> { // setup var task = new TaskEntity(UUID.randomUUID().toString(), "タスク名", "内容"); // exercise var actual = sut.insert(task); // verify assertThat(actual.getCount()).isEqualTo(1); assertThat(actual.getEntity()).isEqualTo(task); assertThat(sut.selectAll()).hasSize(3); }); } @Test void testSelectAll() { transactionManager.required( () -> { // exercise var actual = sut.selectAll(); // verify assertThat(actual).hasSize(2); assertThat(actual.get(0).getTaskId()).isEqualTo("001-dummy"); assertThat(actual.get(1).getTaskId()).isEqualTo("002-dummy"); }); } }
@MicronautTest
をつけることで、Inject できるようにします- テスト開始時に flyway で table 作成するだけでなく、fixture となるデータを sql で入れたかったので、プロパティ
flyway.datasources.default.locations
を上書きますdb_fixtures/minimum
は test/resources ディレクトリに配置しています。
- Dao を Inject することでアノテーションプロセッサで生成した Dao の実装クラスが Inject されます。
- LocalTransactionManager を Inject します
- Inject した LocalTransactionManager を使用してテストします。Dao のテストはトランザクションをテストクラスで制御しています。参考
トランザクション
@Transactional
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Around @Type(TransactionInterceptor.class) // 1 public @interface Transactional {}
@Transactional
がついている時はトランザクションを張るようにします
TransactionInterceptor
@Singleton @RequiredArgsConstructor public class TransactionInterceptor implements MethodInterceptor<Object, Object> { private final LocalTransactionManager transactionManager; @Override public Object intercept(MethodInvocationContext<Object, Object> context) { return transactionManager.required((Supplier<Object>) context::proceed); // 1 } }
- 今回は手を抜いて、propagation は REQUIRED だけにしました
- メソッド呼び出し時にトランザクションを張ります
TaskUseCase
@Singleton @RequiredArgsConstructor @Transactional // 1 public class TaskUseCase { private final TaskRepository taskRepo; ... }
- 今回は UseCase に
@Transactional
を付与しました- このメソッド呼び出しが正常終了すると commit し、Exception を throw すると rollback します
まとめ
micronaut で doma2 を使ってアクセスできるようになりました。 Micronaut Data に Doma2 を...とも思ったのですが、簡単にいかなそうなので小手先でできるようにしてみました。Java の ORM 何が流行ってるのかわからん。
若干やっつけ感はありますが、アノテーションでトランザクション管理できるようになりました。 今回の差分は大体こんな感じです。
*1:超感謝です!