개요
Spring Boot 2.x와 Java 11 환경에서 JPA와 MyBatis를 병행 사용하던 중, Native Query 처리 과정에서 다양한 오류가 발생했습니다. 특히 JPA 엔티티 인식 오류, Optional<Object[]> 처리의 불편함, 패키지 스캔 설정 문제, QueryDSL 관련 빈 생성 오류 등이 복합적으로 발생했습니다. 본 글에서는 이러한 문제들의 원인을 차례대로 분석하고 해결 방법을 정리합니다.
엔티티가 Not a managed type 오류 발생
서비스 클래스에서 JPA Repository를 주입하려 할 때 다음과 같은 오류가 발생했습니다.
Not a managed type: class com.example.domain.entity.ScoreEntity
이 오류는 Spring이 해당 엔티티 클래스를 JPA에서 관리되는 대상으로 인식하지 못할 때 발생합니다.
해결 방법
- 엔티티 클래스에
@Entity
와@Table
을 선언하고, import는 반드시javax.persistence.*
로 해야 합니다. (Spring Boot 2.x 기준)
import javax.persistence.*;
@Entity
@Table(name = "scores")
public class ScoreEntity {
@Id
private Long id;
@Column(name = "score")
private Double score;
// 필요한 컬럼만 선언 가능
}
@EntityScan
설정을 통해 엔티티 패키지가 스캔 대상에 포함되도록 합니다.
@SpringBootApplication
@EnableJpaRepositories(basePackages = "com.example.repository")
@EntityScan(basePackages = "com.example.domain.entity")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
테이블 컬럼을 전부 매핑해야 하는가?
JPA에서는 엔티티 클래스에 테이블의 모든 컬럼을 매핑할 필요는 없습니다. 필요한 컬럼만 골라 선언하면 됩니다. 단, 컬럼을 사용하는 쿼리나 DML에서 누락된 컬럼이 필요할 경우 오류가 발생할 수 있으므로 주의가 필요합니다.
또한, 컬럼 자료형은 가급적 데이터베이스와 일치시켜야 합니다. 예를 들어 DOUBLE
타입은 Double
, VARCHAR
는 String
, TIMESTAMP
는 LocalDateTime
으로 매핑하는 것이 일반적입니다.
Optional<Object[]> 방식의 단점
Native Query를 사용할 때 결과를 Optional<Object[]>
로 받는 방식은 매우 단순하지만 다음과 같은 문제가 있습니다.
- 컬럼 순서에 의존하며, 인덱스 접근 실수가 발생하기 쉬움
- 자료형 캐스팅 실수 가능
- 유지보수가 어려움
Object[] result = repository.findTopScore().get();
String name = (String) result[0];
Double score = (Double) result[1];
DTO 매핑으로 대체하기
보다 안전하고 명확한 방식으로는 DTO 매핑이 있습니다. JPA에서는 세 가지 방식으로 DTO 매핑을 지원합니다.
1. 생성자 기반 DTO 매핑 (JPQL)
public class ScoreDto {
private String name;
private Double score;
public ScoreDto(String name, Double score) {
this.name = name;
this.score = score;
}
// getter 생략
}
@Query("SELECT new com.example.dto.ScoreDto(s.name, s.score) FROM ScoreEntity s WHERE s.score > :threshold")
Optional<ScoreDto> findTopScore(@Param("threshold") Double threshold);
2. 인터페이스 기반 Projection
public interface ScoreProjection {
String getName();
Double getScore();
}
@Query("SELECT s.name AS name, s.score AS score FROM ScoreEntity s WHERE s.score > :threshold")
Optional<ScoreProjection> findTopScore(@Param("threshold") Double threshold);
3. Native Query와 DTO 매핑 (복잡한 경우)
Native Query를 사용할 경우 DTO 매핑을 하려면 @SqlResultSetMapping
과 @NamedNativeQuery
를 사용하는 방식도 있습니다. 그러나 설정이 복잡하고 유지보수가 어려워 실무에서는 생성자 기반 또는 Projection 방식이 더 많이 사용됩니다.
jakarta.persistence.* vs javax.persistence.*
Spring Boot 2.x에서는 반드시 javax.persistence.*
를 사용해야 합니다. jakarta.persistence.*
는 Spring Boot 3.x 이상에서 사용하는 네임스페이스로, Java EE → Jakarta EE로 넘어가면서 변경된 것입니다. Spring Boot 2.x에서는 jakarta.*
를 사용할 경우 인식되지 않아 오류가 발생합니다.
// 잘못된 예시 (Spring Boot 2.x)
import jakarta.persistence.*;
// 올바른 예시
import javax.persistence.*;
JPA와 MyBatis 병행 시 주의사항
MyBatis가 JPA Repository를 잘못 스캔하게 되면 다음과 같은 오류가 발생할 수 있습니다.
Invalid bound statement (not found): com.example.repository.ScoreRepository.findTopScore
이는 @MapperScan
이 JPA Repository까지 스캔하면서 발생한 문제입니다. 이를 해결하려면 MyBatis와 JPA의 스캔 대상을 명확히 분리해야 합니다.
@MapperScan(basePackages = "com.example.mapper")
@EnableJpaRepositories(basePackages = "com.example.repository")
QueryDSL 사용하지 않는데 오류 발생
Spring Boot 프로젝트에서 querydslPredicateArgumentResolver
관련 오류가 발생할 경우, QueryDSL 의존성이 누락되었거나, 필요하지 않은데 Spring이 자동으로 설정하려는 경우입니다.
QueryDSL을 사용하지 않는다면 관련 설정 및 의존성을 제거하는 것이 가장 깔끔합니다. 사용하고자 한다면 의존성 추가 및 Q 클래스 생성, annotation processing 활성화가 필요합니다.
마무리 정리
문제 상황 | 주요 원인 | 해결 방법 |
---|---|---|
엔티티 인식 실패 | @Entity 누락, jakarta import | javax.persistence 사용, @EntityScan 설정 |
Optional<Object[]> 불편 | 인덱스 접근, 타입 불명확 | DTO 또는 Projection 매핑 사용 |
컬럼 일부 누락 가능 여부 | 가능 | 자료형 정확히 맞춰서 필요한 컬럼만 선언 |
JPA + MyBatis 충돌 | @MapperScan 범위 겹침 | 스캔 경로 명확히 분리 |
QueryDSL 관련 오류 | 자동 설정 충돌 | 의존성 제거 또는 설정 보완 |
'개발 (Development) > Java' 카테고리의 다른 글
[Java] MyBatis만 쓰던 내가 JPA를 처음 접했을 때 이해한 구조 정리 (0) | 2025.08.03 |
---|---|
[Java] Spring Boot 프로젝트에서 MyBatis와 JPA를 함께 사용하는 방법과 오류 해결 과정 (0) | 2025.08.03 |
[Java] PostgreSQL의 timestamptz를 Java MyBatis에서 Instant로 받는 방법 (2) | 2025.07.28 |
[Java] ISO 8601 형식의 시간 출력하기 (현재 시간과 과거 시간 구하기) (2) | 2025.07.20 |
[Java] MyBatis foreach에서 빈 배열이 들어올 경우 예외를 방지하는 방법 (2) | 2025.07.20 |