본문 바로가기

DB

Querydsl에서 columnDefinition으로 인한 cast 에러 해결 경험

querydsl에서 처음 보는 이상한 에러를 마주했다,,,

 

API 작업을 위해 mysql 쿼리를 작성하고 -> 실행하고 -> 결과가 잘 나오면 querydsl로 옮긴다. 

게시글 상세조회 쪽을 작성하며 쿼리를 이런 식으로 짰다. 

 

1. MySQL 쿼리

  • 내가 글쓴이를 팔로우했는지 여부 (followYn)
  • 내 글인지 여부 (myFeedYn)
  • 내가 좋아요 했는지 여부 (likeYn)

# 로그인한 사용자의 id는 1, 조회하는 글의 id는 5라고 가정하고 하드코딩함

CASE 
  WHEN EXISTS (
    SELECT 1
    FROM community_follow cf
    WHERE cf.use_yn = 1
      AND cf.following_id = 1
      AND cf.follower_id = c.user_id
  ) THEN TRUE 
  ELSE FALSE
END AS follow_yn,

c.user_id = 1 AS my_feed_yn, 

CASE 
  WHEN EXISTS (
    SELECT 1
    FROM community_like cl
    WHERE cl.like_yn = 1
      AND cl.community_id = 5
      AND cl.user_id = 1
  ) THEN TRUE
  ELSE FALSE
END AS like_yn

 

 

 

2. Querydsl

 

문제가 된 부분은 가운데 my_feed_yn이었는데 

그냥 쿼리를 저렇게 짰으니까,, Querydsl 코드에서도 이렇게 썼다.

community.userId.eq(loginUserId).as("myFeedYn"),

 

근데 실행하면 SQL이 이렇게 나가면서 SQL syntax error 가 터졌다.

 

(c1_0.user_id = cast(? as int unsigned not null comment '유저 ID')) as myFeedYn

 

 

 

3. 원인

 

현재 회사에서는 엔티티로 스키마까지 관리하는 컨벤션이라 @Column(columnDefinition = "int unsigned not null comment '유저 ID'")처럼 DDL 전체를 columnDefinition에 넣는 방식을 쓴다. 그래서 자바에서 Long 타입이더라도 MySQL에 int unsigned 타입으로 생성되도록 한다. 

 

문제는 community.userId.eq(loginUserId).as("myFeedYn")처럼 불리언 비교식을 그대로 SELECT 컬럼에 투영(Projection)한 것이다. 이 경우 JDBC ?(loginUserId 부분)는 본질적으로 언타입드 파라미터라서 DB가 맥락만으로 타입을 확정하기 어렵다. 특히 WHERE 절이 아닌 SELECT 투영식(Projection Expression, 단순 컬럼이 아니라 계산·조건식·함수 같은 표현식을 SELECT 절에 넣은 것)일 때는 비교 결과 자체를 컬럼으로 뽑는 상황이라 Hibernate 입장에서는 타입이 모호해진다. 게다가 자바의 Long과 MySQL의 int unsigned처럼 정밀 매핑이 불일치할 수 있기 때문에, Hibernate는 안전장치로 cast(? as <db type>)를 끼워 넣는다.

여기서 중요한 건 <db type>을 어디서 가져오는가다. Hibernate는 컬럼 메타데이터를 참조하는데, 이때 적어둔 columnDefinition을 타입 정의로 우선 신뢰한다. 원래 columnDefinition의 공식 목적은 'DDL 생성 시 기본 Dialect 대신 이 문자열을 쓰라'인데, Hibernate는 런타임 SQL 생성 시에도 타입 힌트로 이 문자열을 그대로 재사용한다.

따라서 int unsigned not null comment '유저 ID'처럼 순수 타입 외에 제약과 코멘트까지 포함한 문자열을 적어두면 cast(? as int unsigned not null comment '유저 ID')라는 SQL이 만들어져 문법 오류를 낸 것이다.

 

정리하면

  1. SELECT 투영식의 불리언 비교는 파라미터 타입이 모호해져 캐스트가 유도되기 쉽다.
  2. columnDefinition을 전체 DDL로 써두면 그 문자열이 그대로 캐스트 대상 타입으로 들어간다.

 

4. 해결

비교 결과는 명시적 CASE WHEN … THEN TRUE ELSE FALSE END 구문으로 바꿔서 (예: Querydsl CaseBuilder) 파라미터 캐스팅 문제를 피한다. 

new CaseBuilder()
    .when(community.userId.eq(loginUserId))
    .then(true)
    .otherwise(false)
    .as("myFeedYn"),

 

 

=> case when c1_0.user_id = ? then true else false end as myFeedYn

 

 

5. 반성 & 배운 점

  • 애초에 쿼리를 너무 생각 없이(no 센스) 짰던 것 같다ㅠ
  • columnDefinition은 스키마 DDL을 오버라이드하는 용도인데 Hibernate는 이걸 SQL 타입 힌트로도 사용한다.
  • Boolean 비교식을 select 절에 바로 쓰지 말자,, CaseBuilder로 CASE WHEN 구문을 명시하자
  • 팀 컨벤션을 잘 지키자 (자바 -> Hibernate 기본 타입 매핑 규칙과 현재 컨벤션이 다르기 때문에 columnDefinition 사용 필수)

 

 

6. 회고 (DBA가 있는 회사에서 없는 회사로 이직하며,,, )


DBA가 있으면 columnDefinition이나 타입 매핑 같은 세세한 부분은 신경 덜 쓰고 개발에 집중할 수 있지만, DBA가 없는 환경에서는 개발자가 직접 DB 타입 매핑, DDL 정책, JPA 설정까지 잘 이해하고 챙길 줄 알아야 한다.

엔티티에서 스키마까지 관리하는 방식을 쓰다 보면, 단순한 @Column 하나가 DB 스키마 정의뿐 아니라 런타임 SQL 동작에도 직접적인 영향을 준다는 것을 알게 되었다. 

그래서 결국 JPA를 잘 쓰려면 DB 자체에 대한 이해가 깊어야 한다는 걸 느꼈다. 앞으로도 (가끔씩 DBA님을 그리워하며) 시행착오를 겪으면서 배워나가야겠다 ...!

'DB' 카테고리의 다른 글

MySQL VARCHAR vs TEXT  (2) 2025.08.17