1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Doma2のStream処理でコネクションリークが発生し、ClaudeCodeとともに根絶した話

1
Posted at

はじめに

業務で使っているSpring Boot + Doma2構成のプロジェクトで、コネクションリークが発生し、一部の処理でDBのコネクションが枯渇して処理が停止するという事象が発生しました。
原因はDoma2のDAOが返す Stream の close() 忘れで、一度修正したつもりが別の箇所でも同様の実装が残っており、根絶まで複数チケットをまたぎました。

最終的にClaude Codeを使いつつ人力の調査も加えて残存箇所を一括修正し、CLAUDE.md にルールを追記することで再発防止の仕組みを整えました。この記事ではその経緯と対応内容をまとめます。

問題の背景:Doma2のStreamは明示的なcloseが必要

Doma2では、@Select アノテーションに stream = true を指定することで、DAOメソッドが Stream<Entity> を返すように実装できます。

@Select(strategy = SelectType.STREAM)
Stream<SomeEntity> selectAll(SomeCriteria criteria);

このStreamはDBのカーソルと紐づいており、使い終わった後に明示的に close() しないとDBコネクションが解放されません。Stream は AutoCloseable を実装しているので、try-with-resources を使う必要があります。

※やってしまいがちなNG実装
Stream をそのままメソッドチェーンで使ってしまうと、close() が呼ばれません。

// NG: Streamがcloseされない
public List<SomeModel> findAll(final SomeCriteria criteria) {
    return someDao.selectAll(criteria)
        .map(SomeModel::new)
        .toList();
}

一見問題なさそうですが、toList() を呼んだ後も内部のカーソルが残り、コネクションプールが枯渇していきます。

※正しい実装:try-with-resources でStreamをcloseする

  // OK: try-with-resourcesでStreamをclose
public List<SomeModel> findAll(final SomeCriteria criteria) {
    try (val stream = someDao.selectAll(criteria)) {
        return stream.map(SomeModel::new).toList();
    }
}

try ブロックを抜けるタイミングで stream.close() が呼ばれ、DBコネクションが確実に解放されます。

対応の流れ

ClaudeCodeを使って、該当の実装箇所を調査し、一律で対応箇所を洗い出しました。
しかし、理想的にはこれでOKとしたかったところですが、Claudeの対応内容をレビューした際にこの対応ではすべての対応箇所が洗い出されていないことが分かり、再度抜け漏れがないかの影響箇所を調査しました。(影響箇所が10ファイルにわたっていたため、Claudeで一部見落としが発生していた様子。)
現時点でかなり精度よく修正をしてくれるClaudeCodeですが、指示の仕方が甘いとこのように見落としが起こることもあるというのは開発者として注意する必要があると感じました。

このように調査の際に複数ファイルにわたっている事象だと対応漏れがあったものの、人力で影響調査をしたうえで、その際に影響箇所が分かりやすいよう資材上に"FIXME" のコメントをしておくことで、そのあとの対応自体は基本的にClaudeに実施させるように出来ました。

その後のために

修正後、ClaudeCodeに実装を依頼する際に同様のミスが再び混入しないよう、プロジェクトの CLAUDE.md に以下のルールを追記しました。

コーディング規約

  • DomaのDAOメソッドでStreamを返すメソッドを使用する際は、必ずtry-with-resourcesでStreamをクローズする
    • 例:try (val stream = dao.selectAll(criteria)) { return stream.collect(...); }
    • StreamのfindFirst()、map()、filter()などの操作後も必ずStreamをcloseする

※CLAUDE.md はClaude Codeがコードを書く際の指針となるファイルです。このルールを明記しておくことで、今後Claude Codeが生成するコードでも自動的に try-with-resources が使われるようになります。

まとめ

Doma2のStream処理は便利な反面、close忘れがコネクションリークに直結するため注意が必要です。
try-with-resources を徹底することと、それをコーディング規約として明文化しておくことが重要だと改めて感じました。
また、FIXME コメントで「あとで直す箇所」を明示してClaude Codeに一括修正させるという流れは、もれなく機械的に修正させる作業のコスト削減として有効な手法に感じました。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?