OSIV (Open Session in View)

กลไกของ Hibernate ที่เปิด Session ตั้งแต่ request เข้ามา (ก่อนถึง Controller) แล้วปิดตอน response ออกไป เป็นค่า default ของ Spring Boot: spring.jpa.open-in-view=true

Session vs Transaction

  • Session = ห้องทำงาน — Hibernate ใช้ track entity, จัดการ lazy load
  • Transaction = คำสั่งงานในห้อง — สำเร็จทั้งหมด (commit) หรือยกเลิกทั้งหมด (rollback)
  • Session 1 ตัวมีได้หลาย Transaction

ปัญหา (Before Fix)

เมื่อใช้ RoutingDataSource สำหรับ read-write splitting โดยไม่มี LazyConnectionDataSourceProxy:

Request เข้า
  → OSIV เปิด Session → ยืม connection จริงทันที
  → RoutingDataSource เช็ค: readOnly = false (ยังไม่มี @Transactional)
  → เลือก MASTER ❌
  → เข้า Service → @Transactional(readOnly=true) เริ่ม (สายไปแล้ว!)
  → Query ใช้ Master connection เดิม

ผลลัพธ์: Replica ไม่เคยถูกใช้จริงเลย แม้ log จะแสดง “Routing to: REPLICA” — เพราะ log พิมพ์ตอน Aspect ทำงาน (หลัง @Transactional) แต่ connection ถูกยืมไปก่อนแล้วตอน OSIV เปิด Session

วิธีแก้ (After Fix)

แก้ 2 อย่างใน DataSource configuration:

1. afterPropertiesSet()

ต้องเรียกเองเพราะ routingDataSource ไม่ได้เป็น Bean ตรง ๆ อีกแล้ว (ถูก wrap ก่อน return) IoC Container จึงไม่เรียกให้อัตโนมัติ

2. LazyConnectionDataSourceProxy

ครอบ routingDataSource ไว้ ให้ OSIV ได้ connection ปลอมไปก่อน พอมี query จริง ๆ ที่ @Transactional กำหนด readOnly แล้ว จึงค่อยยืม connection จริง

routingDataSource.afterPropertiesSet();  // confirm map พร้อม
return new LazyConnectionDataSourceProxy(routingDataSource); // wrap ด้วย proxy

ลำดับเหตุการณ์หลังแก้

Request เข้า
  → OSIV เปิด Session → ได้ proxy ปลอม (ยังไม่ยืมจาก pool)
  → เข้า Service → @Transactional(readOnly=true) เริ่ม
  → Query จริง → proxy ยืม connection จริงตอนนี้
  → RoutingDataSource เช็ค: readOnly = true
  → เลือก REPLICA ✅

หมายเหตุ HikariCP

HikariCP สร้าง connection ไว้ล่วงหน้าตอน app start แล้วให้ยืม-คืน ปัญหาไม่ใช่เรื่องสร้าง connection แต่เป็นเรื่อง ยืมจาก pool ไหน (Master/Replica) และ ยืมตอนไหน (ก่อน/หลัง @Transactional)

  • Spring Boot — OSIV เปิดเป็น default
  • IoC Container — เกี่ยวข้องกับ afterPropertiesSet()
  • Spring — framework ตัวแม่