본문 바로가기
1인개발자에서 살아남는법!/시리즈

[#4 spring boot] MyBatis 기반 프로젝트, JPA + Thymeleaf로 전환하는 모든 방법 (Spring Boot)

by Alan__kang__morlang 2025. 5. 31.
반응형

Spring Boot + Thymeleaf로 레거시 프로젝트 마이그레이션 및 JPQL 문제 해결 (주니어/1인 개발자 가이드)

 

이 글은 Spring MVC + JSP + MyBatis 레거시 프로젝트를 Spring Boot + JPA + Thymeleaf로 마이그레이션하고, JPQL(@Query) 문제를 해결하는 과정을 주니어 개발자와 1인 개발자가 이해하기 쉽도록 정리한 가이드입니다. 하루 동안 진행한 모든 내용을 빠짐없이 담았습니다.


1️⃣ 기존 레거시 구조 분석

  • Spring MVC + JSP + MyBatis + XML 설정
  • JSP 기반 뷰와 SQL 매퍼(XML) 사용
  • MariaDB 기반 DB, 주요 테이블: CATEGORY_CODE

2️⃣ 마이그레이션 목표 및 진행

  • Spring Boot 3.5.0 + JPA(Hibernate) + Thymeleaf
  • Maven 빌드, WAR 패키징
  • JSP → Thymeleaf 변환 (Layout Dialect 적용)
  • DB 연결: MariaDB
  • Entity 매핑: CategoryCode

3️⃣ CATEGORY_CODE 테이블 정의 및 Entity

테이블 DDL

CREATE TABLE CATEGORY_CODE (
  seq_category int(11) AUTO_INCREMENT PRIMARY KEY,
  category_code varchar(20),
  category_code_up varchar(20),
  category_code_nm varchar(100),
  category_del varchar(5)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

CategoryCode Entity

package com.chopping.adminbridge.recipe.entity;

@Entity
@Table(name = "CATEGORY_CODE")
public class CategoryCode {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "seq_category")
    private Integer seqCategory;

    @Column(name = "category_code")
    private String categoryCode;

    @Column(name = "category_code_up")
    private String categoryCodeUp;

    @Column(name = "category_code_nm")
    private String categoryCodeNm;

    @Column(name = "category_del")
    private String categoryDel;

    // getters, setters, toString()
}

4️⃣ JPQL(@Query) 문제 및 정상화 시도

JPQL 코드

@Query("SELECT c FROM CategoryCode c WHERE c.categoryDel <> 'Y' AND (c.categoryCodeUp = :category OR c.categoryCode = :category)")
List<CategoryCode> findActiveCategoryCodes(@Param("category") String category);

문제 원인

  • JPQL은 Entity 필드명을 기반으로 작성, Hibernate 메타정보와 동기화 필요
  • 하지만 ddl-auto: update 상태에서 Hibernate가 DB 스키마 메타정보를 잘못 인식 가능성
  • DB에서는 정상 동작하지만 Hibernate Prepared Statement에서는 데이터 조회 실패

5️⃣ Native Query 적용 (JPQL 대안)

Native Query 코드

@Query(value = "SELECT * FROM CATEGORY_CODE WHERE category_del <> 'Y' AND (category_code_up = :category OR category_code = :category)", nativeQuery = true)
List<CategoryCode> findActiveCategoryCodes(@Param("category") String category);

Native Query 결과

  • Hibernate 로그: result.size() = 6 (category), result.size() = 9 (timeInfo)
  • DB와 동일하게 동작, JPQL의 문제를 우회

6️⃣ Hibernate 설정 및 ddl-auto: none 예제

spring:
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
    properties:
      hibernate.format_sql: true
      hibernate.use_sql_comments: true

ddl-auto를 none으로 설정하면 Hibernate가 스키마를 변경하지 않아 메타정보 문제 방지


7️⃣ Native Query vs JPQL 비교

구분 Native Query JPQL
문법 SQL 그대로 Entity 필드명 기반
동작 DB 직접 실행 Hibernate SQL 생성
성능 복잡 SQL에서 유리 단순 쿼리 효율적
유연성 DB별 최적화 가능 DB 독립적
단점 DB 종속 메타정보 동기화 필요

8️⃣ Thymeleaf Layout Dialect 적용

  • 레이아웃 구성: layout.html 파일 생성
  • Thymeleaf fragment: <div th:replace="~{fragments/navigation :: navigation}">
  • layout:decorate="~{layout}"으로 공통 레이아웃 적용

9️⃣ 정리 및 추천

  • Entity 매핑은 정상이나 JPQL(@Query)에서는 Hibernate 메타정보 동기화 문제 발생
  • Native Query로 동작 확인 및 데이터 정상 반환
  • JPQL 정상화를 위해 ddl-auto: none, show-sql, format_sql 설정 추천
  • 복잡 쿼리에서는 Native Query 유지도 좋은 선택

도움되셨다면 댓글과 공감 부탁드려요!

반응형