Tech Blog

เหตุใดจึงเลือก MyBatis แทน JPA สำหรับ Spring Boot API Server

by Tech Writer
Spring Boot MyBatis JPA Java

บทนำ

เมื่อสร้าง REST API ด้วย Spring Boot tutorial ส่วนใหญ่ใช้ Spring Data JPA
ตอนแรกผมก็พยายาม implement ด้วย JPA เช่นกัน

อย่างไรก็ตาม สำหรับการ implement DVD rental API ฝั่งลูกค้า เราเลือก MyBatis

บทความนี้ไม่ใช่ “JPA ไม่ดี” — มันเป็นเรื่อง “ทำไม MyBatis ถึงเหมาะกว่าในกรณีนั้น”


จุดที่เราสะดุดกับ JPA

DVD rental DB นั้นอิงจาก PostgreSQL sample DB dvdrental
มีประมาณ 15 table ที่มี relation ซับซ้อนพันกัน

film ←→ film_actor ←→ actor
film ←→ film_category ←→ category
inventory → film
rental → inventory → film
payment → rental

เมื่อพยายามสร้าง endpoint ที่ return “รายการภาพยนตร์ (พร้อม categories)” สำหรับ customer API เราพบปัญหาเหล่านี้กับ JPA

ปัญหาที่ 1: N+1 Queries

เมื่อเชื่อม film และ category ด้วย @ManyToMany query จะรันเพื่อดึง category ทุกครั้งที่ดึงภาพยนตร์ 1 รายการ
ถ้ามี 1,000 ภาพยนตร์ จะ execute SQL 1,001 คำสั่ง

// ถ้ามี 1000 รายการนี้กลายเป็น 1001 SQL statements
List<Film> films = filmRepository.findAll();
films.forEach(f -> f.getCategories()); // ← N+1 เกิดที่นี่

สามารถแก้ไขได้ด้วย @EntityGraph หรือ fetch join แต่ code จะซับซ้อน

ปัญหาที่ 2: Aggregate Queries เขียนยาก

สำหรับการรวมข้อมูลที่ต้องใช้ GROUP BY หรือ subquery — “จำนวนภาพยนตร์ตาม category”, “รายการภาพยนตร์ที่มี stock เหลือ” — การเขียนใน JPQL ลด readability

// การเขียน GROUP BY ใน JPQL จะเป็นแบบนี้
@Query("SELECT c.name, COUNT(fc) FROM FilmCategory fc " +
       "JOIN fc.category c GROUP BY c.name ORDER BY COUNT(fc) DESC")
List<Object[]> countByCategory();

return type กลายเป็น List<Object[]> ทำให้สูญเสีย type safety


เหตุใดจึงเลือก MyBatis

เหตุผลที่ 1: สามารถเขียน SQL ตรงๆ ได้

MyBatis อนุญาตให้เขียน SQL ตรงๆ ใน XML หรือ annotations

<!-- FilmMapper.xml -->
<select id="findAllWithCategory" resultType="PublicFilmSummary">
    SELECT
        f.film_id,
        f.title,
        c.name AS category_name,
        f.length,
        f.description
    FROM film f
    LEFT JOIN film_category fc ON f.film_id = fc.film_id
    LEFT JOIN category c ON fc.category_id = c.category_id
    ORDER BY f.title
</select>

SQL ที่คุณเขียน execute ตามที่เป็น พฤติกรรมคาดเดาได้และ debug ง่าย

เหตุผลที่ 2: Aggregate Queries ที่ซับซ้อนเขียนได้เป็นธรรมชาติ

<select id="findFilmsInStock" resultType="PublicFilmSummary">
    SELECT DISTINCT
        f.film_id,
        f.title,
        COUNT(i.inventory_id) AS stock_count
    FROM film f
    JOIN inventory i ON f.film_id = i.film_id
    LEFT JOIN rental r ON i.inventory_id = r.inventory_id
        AND r.return_date IS NULL
    WHERE r.rental_id IS NULL
    GROUP BY f.film_id, f.title
    HAVING COUNT(i.inventory_id) > 0
</select>

การเขียนสิ่งเดียวกันด้วย JPQL ของ JPA จะซับซ้อนมาก

เหตุผลที่ 3: SQL แม่นยำกว่าเมื่อทำงานกับ DB ที่มีอยู่

dvdrental เป็น DB ที่มีอยู่ ชื่อ table และชื่อ column ถูกกำหนดไว้แล้ว
การเขียน SQL ตรงๆ มีข้อผิดพลาดน้อยกว่าการแนบ annotation @Column(name = "...") กับทุก field ใน JPA


การ Setup MyBatis

<!-- pom.xml -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>
// Mapper interface
@Mapper
public interface PublicFilmMapper {
    List<PublicFilmSummary> findAllWithCategory();
    Optional<PublicFilmDetail> findById(int filmId);
}
# application.yml
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true  # snake_case → camelCase แปลงอัตโนมัติ

เมื่อใดควรใช้ JPA

MyBatis ไม่ใช่คำตอบเสมอ — ขึ้นอยู่กับกรณี

กรณีเทคโนโลยีที่เหมาะ
CRUD อย่างง่ายเป็นหลักJPA (Spring Data JPA)
JOIN และการรวมข้อมูลที่ซับซ้อนมากMyBatis
ทำงานกับ DB ที่มีอยู่MyBatis
ออกแบบ DB ตั้งแต่ต้นJPA
ต้องการ type-safe query builderjOOQ

สรุป

  • DVD rental API ฝั่งลูกค้ามี JOIN และการรวมข้อมูลที่ซับซ้อนมาก จึงเลือก MyBatis
  • MyBatis อนุญาตให้เขียน SQL ตรงๆ ทำให้ทำงานกับ DB ที่มีอยู่ได้ง่ายและคาดเดาพฤติกรรมได้
  • ไม่ต้องกังวลเรื่อง N+1 SQL ที่เขียน execute ตรงๆ — นำไปสู่ debug ที่ง่ายขึ้น
  • “อันไหนดีกว่า” เป็นคำถามที่ผิด “เลือกตามสิ่งที่คุณกำลังสร้าง” คือคำตอบที่ถูกต้อง

แผนที่บทความของ Series นี้

การสร้างแอพ DVD Rental สำหรับผู้ใช้ควบคู่กับหน้าจัดการ — ภาพรวมโครงสร้าง Vue 3 + Spring Boot

ส่งข้อความได้ตามสบาย

กรุณาส่งข้อความ หากมีคำปรึกษาด้านเทคนิค ความคิดเห็น หรือคำถาม