
やっぱり 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;
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public Dialect getDialect() {
return new H2Dialect();
}
@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
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
@Property(name = "flyway.datasources.default.locations", value = "db/migration,db_fixtures/minimum")
class TaskDaoTest {
@Inject private TaskDao sut;
@Inject private LocalTransactionManager transactionManager;
@Test
void testInsert() {
transactionManager.required(
() -> {
var task = new TaskEntity(UUID.randomUUID().toString(), "タスク名", "内容");
var actual = sut.insert(task);
assertThat(actual.getCount()).isEqualTo(1);
assertThat(actual.getEntity()).isEqualTo(task);
assertThat(sut.selectAll()).hasSize(3);
});
}
@Test
void testSelectAll() {
transactionManager.required(
() -> {
var actual = sut.selectAll();
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)
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);
}
}
- 今回は手を抜いて、propagation は REQUIRED だけにしました
TaskUseCase
@Singleton
@RequiredArgsConstructor
@Transactional
public class TaskUseCase {
private final TaskRepository taskRepo;
...
}
- 今回は UseCase に
@Transactional
を付与しました
- このメソッド呼び出しが正常終了すると commit し、Exception を throw すると rollback します
まとめ
micronaut で doma2 を使ってアクセスできるようになりました。
Micronaut Data に Doma2 を...とも思ったのですが、簡単にいかなそうなので小手先でできるようにしてみました。Java の ORM 何が流行ってるのかわからん。
若干やっつけ感はありますが、アノテーションでトランザクション管理できるようになりました。
今回の差分は大体こんな感じです。