Spring Bootアプリを起動すると、何も設定していないのに spring.jpa.open-in-view is enabled by default... というWARNがログに流れます。OSIV(Open Session In View)が黙って有効になっていることの通知です。ただし警告文が求めているのは「明示的に設定せよ」だけで、trueとfalseのどちらにすべきかは教えてくれません。既定値の根拠と警告の発火条件、falseへ切り替える前に依存箇所を洗い出す手順、切り替え後に出る LazyInitializationException の直し方までを、Spring Boot 4.2.0-RC1と4.1.2のソースで確かめた内容で扱います。
まとめ
spring.jpa.open-in-view は、JPAの永続化コンテキスト(EntityManager)をHTTPリクエストの処理全体に束縛するかどうかのスイッチです。未設定なら有効、つまり既定値はtrue。2026年10月22日に出たSpring Boot 4.2.0-RC1・4.1.2と、3.5系のOSS最終版3.5.16(2026年6月25日)のどれでも変わりません。起動時のWARNが出るのは「値が未設定のとき」だけで、spring.jpa.open-in-view=true と明示すれば挙動は変わらないまま警告だけが消えます。
falseにするかは、ビュー層やJSON変換の時点で遅延ロードに頼っているコードがあるかで決めてください。いきなり本番の設定を変えず、テスト用プロファイルでfalseにして統合テストを全エンドポイントに通し、落ちた箇所を直してから切り替えます。切り替え後に出る LazyInitializationException は、OSIVが隠していた取得設計の穴です。JOIN FETCH、@EntityGraph、DTOプロジェクションのいずれかで、必要なデータをトランザクション内に取り切ります。
spring.jpa.open-in-viewの実体と、効く範囲の限界
既定値trueの根拠はmatchIfMissing=trueの条件式
このプロパティの既定値は、ドキュメントの記述ではなく自動構成の条件式が決めています。Spring Bootの JpaBaseConfiguration に入れ子で定義された JpaWebConfiguration には、次の条件が付いています。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(WebMvcConfigurer.class)
@ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class })
@ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class)
@ConditionalOnBooleanProperty(name = "spring.jpa.open-in-view", matchIfMissing = true)
protected static class JpaWebConfiguration {
最後の matchIfMissing = true が既定値の正体です。プロパティが存在しない場合も条件が成立し、OpenEntityManagerInViewInterceptor がBeanとして登録されます。この6行は4.2.0-RC1、4.1.2、3.5.16の各タグで同一でした。@ConditionalOnBooleanProperty 自体はSpring Boot 3.5で導入された注釈なので、3.4以前でソースを照合するときは @ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true) を探してください。公式リファレンスの「Open EntityManager in View」も、Webアプリでは既定でこのインターセプタを登録し、望まないならfalseにせよ、と書いています。
登録される OpenEntityManagerInViewInterceptor はSpring Framework側のクラスで、preHandle で EntityManager を生成して TransactionSynchronizationManager.bindResource でスレッドへ束縛し、afterCompletion で解除してクローズします。postHandle は空実装。つまりコントローラに入る前から、レスポンスを書き終えた後まで、同じ永続化コンテキストが生き続けます。
ここでのSessionはHTTPセッションではなく永続化コンテキスト
Open Session In Viewという名前のSessionは、Hibernateの Session、JPAでいう EntityManager を指します。ログイン状態を保持するHTTPセッション(HttpSession)とは無関係です。
この取り違えは実害を生みます。セッションタイムアウトを短くしてもセッションIDを再生成しても、OSIVの挙動は変わりません。逆にOSIVを無効化してもログイン状態には影響しません。spring.jpa.open-in-view の文脈で出てくる「セッションが長く開いたまま」は、1次キャッシュを抱えた永続化コンテキストがリクエスト終端まで残るという意味です。JPAとHibernateの関係が曖昧なら、Java ORM(O/Rマッパー)の種類と選び方|Hibernate・MyBatis・jOOQを比較【2026年10月】で仕様と実装の分担を整理しています。
Servletスタック限定:WebFlux・バッチ処理が対象外になる条件
上の条件式には @ConditionalOnWebApplication(type = Type.SERVLET) と @ConditionalOnClass(WebMvcConfigurer.class) が付いています。Spring WebFluxだけを使うリアクティブアプリでは、この自動構成は最初から成立しません。trueにしてもfalseにしても何も起こらない、ということです。WebFluxとMVCの構成の違いはSpring WebFluxとは?MVCとの違い・Mono/Fluxの基本とSpring Boot 4.1での使い方で扱っています。@ConditionalOnMissingBean と @ConditionalOnMissingFilterBean も効くため、自前で OpenEntityManagerInViewInterceptor かサーブレットフィルタ版の OpenEntityManagerInViewFilter を登録しているアプリでも、このプロパティは無視されます。
同じことが @Scheduled のバッチ、メッセージリスナー、サービスメソッドを直接呼ぶテストなど、HTTPリクエスト以外の入口すべてに当てはまります。同じロジックを複数の入口へ露出する設計なら、OSIVに依存しない書き方へ寄せておかないと入口ごとに挙動が割れます。
起動時WARNが出る条件と、消し方の分岐
警告の発火条件はgetOpenInView()のnull判定
Bean定義メソッドの中身を見ると、警告の発火条件は明快です。
@Bean
public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
if (this.jpaProperties.getOpenInView() == null) {
logger.warn("spring.jpa.open-in-view is enabled by default. "
+ "Therefore, database queries may be performed during view "
+ "rendering. Explicitly configure spring.jpa.open-in-view to disable this warning");
}
return new OpenEntityManagerInViewInterceptor();
}
JpaProperties の openInView フィールドは @Nullable Boolean で、未設定ならnullのままです。判定しているのは値がtrueかどうかではなく、開発者が意思表示をしたかどうかでした。この警告はspring-bootのissue #7107(2016年10月5日起票、2017年10月30日クローズ、マイルストーン2.0.0.M6)で入りました。起票時の要求は「OSIVはアンチパターンなのでオプトインにすべきだ」でしたが、既定値は変えず通知だけ足す決着になっています。
検索で見かける誤った回避策が、ロガーごと黙らせるやり方です。ロガー名は org.springframework.boot.jpa.autoconfigure.JpaBaseConfiguration$JpaWebConfiguration(3.5以前は org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration)です。ログレベルで潰せば確かに静かになりますが、消えるのは警告だけで、判断した記録も一緒に消えます。
trueを明示する場合とfalseにする場合の線引き
選択肢は事実上3つで、それぞれ意味が違います。
| 設定 | OSIV | 起動時WARN | 意味 |
|---|---|---|---|
| 未設定 | 有効 | 出る | 未判断のまま既定に乗っている |
| true を明示 | 有効 | 出ない | 有効のまま使うと決めた |
| false | 無効 | 出ない | 取得プランを自前で管理すると決めた |
書き方はプロパティ1行です。application.properties なら上、application.yml なら下になります。
spring.jpa.open-in-view=false
spring:
jpa:
open-in-view: false
falseへ倒すべきなのは、レスポンスをJSONで返すAPIサーバで、レイテンシやコネクションプールの逼迫を実測で問題視している場合です。新規プロジェクトなら初日にfalseを書いておくのが最も安上がりです。逆にtrueのままでよいのは、Thymeleafでサーバサイドレンダリングしていて画面数が限られ、同時接続も想定内に収まっている管理画面のような領域。既存の大規模アプリで、統合テストが全エンドポイントを踏めていないなら、まだ倒さないでください。falseにして壊れる箇所が本番で初めて露出する事故のほうが、コネクション占有時間より高くつきます。既存のSpring Bootアプリで取得設計ごと見直す規模になるなら、フルスクラッチ開発の範囲で改修を相談する選択肢もあります。
OSIV有効時の永続化コンテキスト寿命と接続リースの実像
リクエスト終端まで生き残る永続化コンテキストと1次キャッシュの滞留
サービス層のトランザクションがコミットしても、EntityManager は閉じません。閉じるのは afterCompletion、つまりレスポンスを書き終えた後です。その間、読み込んだエンティティはすべて1次キャッシュに滞留します。
一覧画面で数千件のエンティティを読み、DTOへ詰め替えて返す処理を考えてください。詰め替えが終わってもマネージド状態のエンティティは解放されず、リクエストが終わるまでヒープに残ります。issue #47547の起票者が挙げた被害も、コネクションプールの枯渇(connection pool starvation)と並んで「セッションの寿命が延びることによるメモリリーク」でした。
「接続を握りっぱなし」という説明の精度
OSIVの解説では「リクエストの間ずっとDB接続を占有する」と書かれます。Hibernate単体の既定だけを見るとこれは不正確に見えます。hibernate.connection.handling_mode の既定値は DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION で、接続は必要になった時点で取得され、トランザクションの完了ごとに解放されるからです。
ところがSpring Bootの既定構成では、この既定が使われていません。Bootが作る HibernateJpaVendorAdapter は、JTAでない永続化ユニットでは DELAYED_ACQUISITION_AND_HOLD を強制します。javadocには「ユーザー指定の接続処理モードがない限り、ベンダーアダプタが DELAYED_ACQUISITION_AND_HOLD を手動で強制する」とあります。つまり接続は最初に必要になった時点で取得され、EntityManager が閉じるまで保持されます。OSIVが有効なら閉じるのは afterCompletion なので、最初のSQLからレスポンスを書き終えるまで同じ接続を握り続けます。Spring Bootに限れば、「握りっぱなし」はおおむね正確な説明です。Hibernate単体の既定に戻したいなら、spring.jpa.properties.hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION を明示します。
Vlad Mihalcea氏の解説はこの状態を「データベース接続がUI描画フェーズを通じて保持され、接続のリース時間が延び、コネクションプールの輻輳によって全体のトランザクションスループットが制限される」と表現し、さらに「UI描画フェーズから発行される追加の文はすべてauto-commitモードで実行される」と指摘しています。
実際のリース時間はHibernateの設定と接続プールの実装で変わります。伝聞で決めず、HikariCPのメトリクスで測ってください。Micrometer経由なら hikaricp.connections.usage が1接続あたりの利用時間、hikaricp.connections.acquire が取得待ち時間、hikaricp.connections.pending が接続を待っているスレッド数です。OSIVを切る前後でこの3つを比べれば、自分のアプリで効果があるかを数字で判断できます。プールサイズやタイムアウトの既定値はHikariCPとは?既定値一覧とSpring Bootでの設定・タイムアウト対処【7.1.0】にまとめています。
ビュー描画とJSON変換で走るN+1クエリ
OSIVはテンプレートエンジンを使う画面だけの話ではありません。@RestController がエンティティをそのまま返す場合、JacksonによるJSON変換もOSIVの守備範囲で動きます。変換中に遅延ロードのコレクションへ触れれば、そこでSQLが発行されます。
親エンティティを100件返すAPIが各要素の子コレクションをJSONへ含めると、SQLは1+100本になります。これが典型的なN+1で、しかもサービス層のログには現れず、コントローラのメソッドが返った後に起きるため原因が追いにくい。発行クエリ数の数え方とORM別の対策はN+1問題とは?発行クエリ数の実測と検出方法・ORM別の対策で扱っています。
falseへ切り替える前の依存箇所の洗い出し手順
OSIVに頼っている箇所はコードを読んでも見つかりません。遅延ロードはテンプレートやJacksonがgetterを呼んだ瞬間に暗黙に走るので、実行して落とすのが確実です。
テスト用プロファイルでのfalse化と統合テスト
本番の設定は触らず、テストだけでOSIVを切ります。@SpringBootTest の properties 属性で上書きすれば、テストクラス単位で切り替えられます。
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
@SpringBootTest(properties = "spring.jpa.open-in-view=false")
@AutoConfigureMockMvc
class ParentApiTest {
@Autowired
MockMvc mvc;
@Test
void listParents() throws Exception {
mvc.perform(get("/api/parents"))
.andExpect(status().isOk());
}
}
Spring Boot 4.x では @AutoConfigureMockMvc が spring-boot-webmvc-test モジュール(パッケージ org.springframework.boot.webmvc.test.autoconfigure)へ移っており、依存には spring-boot-starter-webmvc-test を足します。3.5以前のimportをコピーすると解決できません。全エンドポイントを1回ずつ叩くテストがあれば、500で落ちたものがそのままOSIV依存の一覧になります。
@Transactional付きテストによるOSIV依存の隠蔽
上のテストに @Transactional を付けてはいけません。付けると、falseにしたのに例外が出ず、全部通ってしまいます。
Spring Bootのテストのドキュメントは、@Transactional なテストは各メソッドの終わりにロールバックするとしたうえで、「RANDOM_PORT か DEFINED_PORT では本物のサーブレット環境になるため、HTTPクライアントとサーバは別スレッド、つまり別トランザクションで動く」と書いています。裏を返すと、既定の MOCK 環境でMockMvcを使う場合、リクエスト処理はテストと同じスレッドで走ります。テストメソッドが開いたトランザクションの EntityManager がJSON変換の時点まで生きているので、OSIVを切っても遅延ロードが成功します。テストの中でOSIVを自作しているのと同じ状態です。
洗い出し用のテストは @Transactional を外し、データの後始末は @Sql や削除処理で行ってください。webEnvironment = WebEnvironment.RANDOM_PORT にして実HTTPで叩く方法でも、この罠は避けられます。
SQLログとセッション統計によるコントローラ復帰後SQLの特定
テストが薄いアプリでは、OSIVを有効にしたまま「トランザクションの外で走ったSQL」を探します。org.hibernate.SQL ロガーをDEBUGにすると、発行されたSQLがすべて出ます。
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.session.metrics=debug
2行目はHibernate 7.2以降(Spring Boot 4.x)のセッション統計で、ソース上はこのロガーがDEBUGのときだけ集計が有効になり、セッションの終わりに「executing N JDBC statements」を含む行を出します。OSIVが有効なら1リクエスト=1セッションなので、1回のAPI呼び出しで何本のSQLが走ったかがそのまま分かります。一覧APIで件数に比例して本数が増えるなら、そこがOSIVに頼ったN+1です。SQLログでは、サービスメソッドの終了ログより後ろに出ているSELECTが、falseにした瞬間に落ちる箇所に当たります。Spring Boot 3.5(Hibernate 6.6)にはこのロガーがないので、spring.jpa.properties.hibernate.generate_statistics=true と logging.level.org.hibernate.engine.internal.StatisticalLoggingSessionEventListener=info の組み合わせを使います。
falseへ切り替えた直後のLazyInitializationException対策
Hibernateの版による例外メッセージの違い
falseにして最初に見るのはこの例外です。メッセージの文面は同梱Hibernateの版で違うので、検索するときは自分の版の文言で探してください。
| 対象 | Hibernate 6.6(Boot 3.5) | Hibernate 7.x(Boot 4.x) |
|---|---|---|
| コレクション | failed to lazily initialize a collection of role: … |
Cannot lazily initialize collection of role '…' with key '…' |
| 末尾の理由 | could not initialize proxy - no Session |
(no session) |
| 単一の関連 | Could not initialize proxy [エンティティ名#ID] - no session |
6.6と同じ |
7.x の文言にはキーが入るので、どの親(主キー)のコレクションで落ちたかがログだけで分かります。role は com.example.Parent.children のような「エンティティのクラス名+フィールド名」で、ここに出たフィールドが次の3つの手当ての対象です。
JOIN FETCHによる一括取得と、Hibernate版で変わる制約
最も直接的な手当てが、JPQLの join fetch です。関連を同じSQLで取得するので、トランザクションを抜けた後に触っても初期化済みです。素の join fetch は内部結合なので、子が0件の親も返すなら left join fetch にします。
@Query("select p from Parent p left join fetch p.children where p.status = :status")
List<Parent> findWithChildren(@Param("status") Status status);
ここで distinct を書いていないのは意図的です。Hibernate 6.0の移行ガイドは、子コレクションをjoin fetchする際に親の重複を除く目的で distinct を書く必要はなくなったと明記し、あわせて「distinct は常にSQLクエリへ渡される」としています。惰性で付けるとSQL側に SELECT DISTINCT がそのまま出て、フェッチした全列の比較を無駄に走らせるだけです。Hibernate 5世代の作法として広まった書き方なので、コピー元の年式に注意してください。
ページングとの併用は、同梱しているHibernateの版で結論が変わります。各版の spring-boot-dependencies で確かめた同梱版は次のとおりです。
| Spring Boot | 同梱Hibernate | コレクションフェッチ+ページング |
|---|---|---|
| 3.5.16 | 6.6.53.Final | メモリ内で処理(HHH90003004) |
| 4.0.9 | 7.2.25.Final | メモリ内で処理(HHH90003004) |
| 4.1.2 | 7.4.13.Final | SQLのlimitで処理 |
| 4.2.0-RC1 | 7.4.13.Final | SQLのlimitで処理 |
7.2系まではコレクションフェッチとページングを併用すると全件を読んでJVM側でページングし、firstResult/maxResults specified with collection fetch; applying in memory の警告とともに大量データで破綻します。Hibernate 7.4の移行ガイドは「limitはSQLクエリの一部として処理されるようになった。以前の挙動に戻すにはクエリヒント org.hibernate.limitInMemory を設定する」としています。同じ4系でも、4.0系なら制約が残り、4.1系から解消している点に注意してください。
複数コレクションの同時フェッチには別の制約があります。MultipleBagFetchException のメッセージは cannot simultaneously fetch multiple bags: [...] で、対象はbag、すなわち @OrderColumn を持たない List が2つ以上の場合です。両方を Set にすれば例外は避けられますが、今度は結果がデカルト積になります。件数が伸びる関連は、1本のクエリに詰め込まず複数クエリへ割るのが実務解です。
@EntityGraphによる取得プランの用途別出し分け
クエリ本体は変えずに取得プランだけを差し替えたいなら、Spring Data JPAの @EntityGraph を使います。
@EntityGraph(attributePaths = {"children"})
List<Parent> findByStatus(Status status);
同じエンティティに対して、用途ごとに違う取得プランを持てるのが利点です。一覧では子を取らず、詳細では取る、という出し分けがリポジトリのメソッド単位で書けます。エンティティ側に FetchType.EAGER を書いてしまうと全ユースケースに影響が及ぶので、取得の粒度はマッピングではなくクエリ側で決めてください。@EntityGraph を含むアノテーションの使い分けはSpring Bootのアノテーション一覧|仕組みと使い分け・Boot 4での変更点にまとめています。
DTOプロジェクションによる遅延ロードの排除
本命はこれです。エンティティを返さずDTOへ直接射影すれば、遅延ロードという概念自体が消えます。
@Query("select new com.example.dto.ParentSummary(p.id, p.name, count(c)) "
+ "from Parent p left join p.children c group by p.id, p.name")
List<ParentSummary> findSummaries();
Vlad Mihalcea氏も「DTOプロジェクションはアプリケーション開発者に必要なデータセットだけを取得させ、LazyInitializationException の影響を受けない」として、OSIVが存在しなくてよい状態への移行手段に挙げています。エンティティとDTOの書き分けはエンティティクラスとは?JPAとEF Coreの書き方・DTOとの違いを実行結果で解説、型安全にクエリを組み立てたい場合はQueryDSLとは?Spring Bootでの導入手順と5.1.0・OpenFeign版7.5の選び方が使えます。
3つのうちどれを主軸にするかで迷うなら、参照系はDTOプロジェクション、更新のためにエンティティが要る場面だけ @EntityGraph か join fetch、と決め打ちするのが扱いやすい配分です。エンティティを返す既存コードをすぐ書き換えられない場合は、@Transactional(readOnly = true) を付けたサービスメソッドの中でDTOへ詰め替えてから返すだけでも、例外は止まります。境界をどこに引くかは、サービスクラスとは?Java・Springでの役割と書き方、コントローラーとの違いで扱っているサービス層の責務の切り方と同じ問題です。
hibernate.enable_lazy_load_no_transで黙らせてはいけない理由
検索すると、spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true で例外を消す方法が出てきます。使わないでください。この設定は永続化コンテキストの外で遅延ロードされるたびに一時的なセッションを開いて読み込むもので、Hibernate 7.4.13のソースでは @Unsafe が付き、javadocに「トランザクション分離を簡単に壊し、データのエイリアシング(同じ行が別インスタンスとして読まれる)を招くため推奨しない」「この設定の使用は推奨されない」と書かれています。
OSIVをfalseにしてこの設定をtrueにするのは、OSIVより悪い状態です。遅延ロード1回ごとに一時セッションが接続を取得し直すため、N+1がそのまま接続取得のN+1になり、しかも各読み込みは別々の永続化コンテキストで行われるので整合が取れません。
既定値trueの今後:Spring Boot 4.2でも据え置き
「いずれ既定値がfalseになるまで待つ」という判断は成り立ちません。2025年10月11日、issue #47547「Reconsider disabling ‘spring.jpa.open-in-view’ by default for Spring Boot 4.0」が起票されました。理由は、コネクションプールの枯渇とセッション寿命の延長によるメモリリークを繰り返し踏んできたこと、同じコードが呼び出し元(Webかそれ以外か)で挙動を変えることでした。
クローズは2025年10月15日、ステータスは not_planned、ラベルは「status: declined」。Phil Webb氏のコメントはこうです。「本日議論した。spring.jpa.open-in-view の既定設定はいずれ見直したいが、4.0.0-RC1がこれほど近い時期に変更を加える準備はできていない。すべてを考慮して、現状のままにする」。2026年11月2日時点でこのissueは閉じたままで、2026年10月22日の4.2.0-RC1でも条件式は matchIfMissing = true のままでした。
見直す意思はチームにあるものの、4.0から4.2のRCまで据え置かれました。将来この既定が反転したとき挙動の変化を被るのは、値を明示していないアプリだけです。Spring Boot 4系への移行を検討中なら、Spring Boot 4とは?最新バージョン4.1の変更点・新機能とSpring Boot 3との違い【2026年10月】で他の破壊的変更と併せて確認しておくと一度で片付きます。
よくある質問
spring.jpa.open-in-view の既定値は何ですか?
trueです。プロパティを書かなければ有効になります。根拠は JpaBaseConfiguration.JpaWebConfiguration に付いた @ConditionalOnBooleanProperty(name = "spring.jpa.open-in-view", matchIfMissing = true) で、プロパティが存在しない場合も条件が成立して OpenEntityManagerInViewInterceptor が登録されます。Spring Boot 4.2.0-RC1、4.1.2、3.5.16で同一の記述でした。
「spring.jpa.open-in-view is enabled by default…」の警告はどう消しますか?
application.properties に spring.jpa.open-in-view の値を明示すれば消えます。この警告は値がtrueかどうかではなく、値が未設定(null)かどうかで発火するためです。ただしtrueを明示した場合、消えるのは警告だけでOSIVは有効なまま。falseにするかtrueで確定するかを決めてから書いてください。
OSIVとOpen Session In Viewは同じものですか?
同じです。OSIVはOpen Session In Viewの略で、JPAを使う文脈ではOpen EntityManager In View(OEMIV)とも呼ばれます。Spring Bootが登録するクラス名も OpenEntityManagerInViewInterceptor です。ここでのSessionはHibernateの Session(JPAの EntityManager)を指し、ログイン状態を保持するHTTPセッションとは関係ありません。
spring.jpa.open-in-view=false にすれば必ず速くなりますか?
なりません。取得プランを書き換えないままfalseにした場合、速くなるのではなく LazyInitializationException で落ちるだけです。効果を判断するなら、切り替え前後で hikaricp.connections.usage(接続の利用時間)と hikaricp.connections.pending(接続待ちスレッド数)を比較してください。
falseにしたのにテストでLazyInitializationExceptionが出ないのはなぜですか?
テストメソッドに @Transactional が付いている可能性が高いです。既定の MOCK 環境でMockMvcを使うと、リクエスト処理がテストと同じスレッド・同じトランザクションで走るため、JSON変換の時点でも永続化コンテキストが開いたままになります。@Transactional を外すか、RANDOM_PORT で実HTTPを叩いてください。WebFluxや @Scheduled のバッチではそもそもOSIVが登録されないので、プロパティの値に関係なく同じ結果になります。