JPA(Java Persistence API)의 EntityManager를 사용하여 데이터베이스에 네이티브 SQL 쿼리를 실행하고, 그 결과를 자바 엔티티 객체로 매핑하는 방법을 다룹니다. 특히, 이 문서는 Oracle 데이터베이스 환경을 기준으로 합니다.
네이티브 SQL 조회 결과를 엔티티 객체로 변환하는 핵심은 Query query = entityManager.createNativeQuery(sql, YourEntity.class); 메서드를 사용하는 것입니다. 이 메서드는 SQL 쿼리의 결과 셋을 지정된 엔티티 클래스의 인스턴스로 자동 매핑합니다. 이 기능을 올바르게 활용하려면 엔티티 클래스에는 @Entity 어노테이션과 함께 기본 키를 나타내는 @Id 어노테이션이 반드시 선언되어야 합니다.
엔티티 클래스 정의: EnrollmentRecord
다음은 예제에서 사용할 EnrollmentRecord 엔티티 클래스입니다. 데이터베이스 테이블의 컬럼 이름과 SQL 조회 시의 ALIAS 이름이 엔티티 필드 이름과 일치하도록 정의하는 것이 중요합니다. @Column 어노테이션은 필드 이름과 실제 데이터베이스 컬럼 이름이 다를 때 명시적인 매핑을 제공합니다.
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Column;
@Entity // 이 클래스가 JPA 엔티티임을 나타냅니다.
public class EnrollmentRecord {
@Id // 엔티티의 기본 키를 나타냅니다.
@Column(name = "COURSE_NUM_COL") // DB 컬럼명 'COURSE_NUM_COL'을 'courseNumber' 필드에 매핑
private Long courseNumber;
@Column(name = "STUDENT_ID_COL") // DB 컬럼명 'STUDENT_ID_COL'을 'studentIdentifier' 필드에 매핑
private Long studentIdentifier;
@Column(name = "INFO_COL") // DB 컬럼명 'INFO_COL'을 'additionalInfo' 필드에 매핑
private String additionalInfo;
// JPA가 필요로 하는 기본 생성자
public EnrollmentRecord() {}
// 모든 필드를 포함하는 생성자 (선택사항)
public EnrollmentRecord(Long courseNumber, Long studentIdentifier, String additionalInfo) {
this.courseNumber = courseNumber;
this.studentIdentifier = studentIdentifier;
this.additionalInfo = additionalInfo;
}
// Getter 및 Setter 메서드
public Long getCourseNumber() { return courseNumber; }
public void setCourseNumber(Long courseNumber) { this.courseNumber = courseNumber; }
public Long getStudentIdentifier() { return studentIdentifier; }
public void setStudentIdentifier(Long studentIdentifier) { this.studentIdentifier = studentIdentifier; }
public String getAdditionalInfo() { return additionalInfo; }
public void setAdditionalInfo(String additionalInfo) { this.additionalInfo = additionalInfo; }
@Override
public String toString() {
return "EnrollmentRecord [courseNumber=" + courseNumber + ", studentIdentifier=" + studentIdentifier + ", additionalInfo=" + additionalInfo + "]";
}
}
위 EnrollmentRecord 엔티티는 STUDENT_ENROLLMENT_TBL 테이블의 레코드를 나타냅니다. SQL 쿼리에서 AS 키워드를 사용하여 엔티티 필드 이름과 동일한 ALIAS를 부여하면, @Column 어노테이션 없이도 매핑될 수 있지만, 명시적으로 어노테이션을 사용하는 것이 코드의 가독성과 유지보수성을 높이는 데 도움이 됩니다.
네이티브 SQL 조회 메서드 구현
다음은 EntityManager를 사용하여 다양한 유형의 네이티브 SQL 쿼리를 실행하는 예제 코드입니다. 이 예제는 Spring 프레임워크 환경을 가정하여 @Repository 및 @PersistenceContext를 사용합니다.
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Repository;
@Repository
public class EnrollmentRepository {
@PersistenceContext // JPA EntityManager를 주입받습니다.
private EntityManager entityManager;
/**
* 지정된 학수 번호와 정확히 일치하는 수강 기록을 조회합니다.
* @param courseNum 조회할 학수 번호
* @return 일치하는 EnrollmentRecord 목록
*/
public List<EnrollmentRecord> findByExactCourseNumber(Long courseNum) {
// SQL 쿼리. SELECT 절의 ALIAS는 EnrollmentRecord 엔티티의 필드 이름과 일치해야 합니다.
String sql = "SELECT e.COURSE_NUM_COL AS courseNumber, " +
" e.STUDENT_ID_COL AS studentIdentifier, " +
" e.INFO_COL AS additionalInfo " +
"FROM STUDENT_ENROLLMENT_TBL e " +
"WHERE e.COURSE_NUM_COL = :courseParam";
// 네이티브 SQL 쿼리 생성 및 결과를 EnrollmentRecord 클래스로 매핑 설정
Query query = entityManager.createNativeQuery(sql, EnrollmentRecord.class);
// 쿼리 파라미터 설정
query.setParameter("courseParam", courseNum);
return query.getResultList();
}
/**
* 학수 번호가 특정 패턴을 포함하는 수강 기록을 조회합니다. (부분 일치)
* 이 쿼리는 Oracle 데이터베이스의 LIKE 연산자 및 문자열 연결( || )을 사용합니다.
* @param courseNumPattern 검색할 학수 번호의 부분 패턴
* @return 패턴에 일치하는 EnrollmentRecord 목록
*/
public List<EnrollmentRecord> searchByCourseNumberPartial(Long courseNumPattern) {
String sql = "SELECT e.COURSE_NUM_COL AS courseNumber, " +
" e.STUDENT_ID_COL AS studentIdentifier, " +
" e.INFO_COL AS additionalInfo " +
"FROM STUDENT_ENROLLMENT_TBL e " +
"WHERE e.COURSE_NUM_COL LIKE '%' || :coursePattern || '%'"; // Oracle에서 문자열 연결 '||' 사용
Query query = entityManager.createNativeQuery(sql, EnrollmentRecord.class);
query.setParameter("coursePattern", courseNumPattern);
return query.getResultList();
}
/**
* 학수 번호 패턴에 따라 페이지네이션을 적용하여 수강 기록을 조회합니다.
* 이 예제는 Oracle 데이터베이스의 ROWNUM 기반 페이지네이션 방식을 사용합니다.
* @param courseNumPattern 검색할 학수 번호의 부분 패턴
* @param pageNumber 조회할 페이지 번호 (0부터 시작)
* @param pageSize 페이지당 항목 수
* @return 해당 페이지의 EnrollmentRecord 목록
*/
public List<EnrollmentRecord> findPagedEnrollments(Long courseNumPattern, int pageNumber, int pageSize) {
// 페이지네이션을 위해서는 결과의 일관성을 위해 반드시 ORDER BY 절이 포함되어야 합니다.
String baseSql = "SELECT e.COURSE_NUM_COL AS courseNumber, " +
" e.STUDENT_ID_COL AS studentIdentifier, " +
" e.INFO_COL AS additionalInfo " +
"FROM STUDENT_ENROLLMENT_TBL e " +
"WHERE e.COURSE_NUM_COL LIKE '%' || :coursePattern || '%' " +
"ORDER BY e.COURSE_NUM_COL";
// Oracle ROWNUM을 이용한 페이지네이션 쿼리 구성
int startRow = pageNumber * pageSize + 1; // 시작 행 번호 (1-based)
int endRow = (pageNumber + 1) * pageSize; // 끝 행 번호
String paginatedSql = "SELECT * FROM (" +
" SELECT inner_query.*, ROWNUM rnum FROM (" +
baseSql +
") inner_query WHERE ROWNUM <= :endRow" + // ROWNUM은 조건보다 작거나 같아야 합니다.
") WHERE rnum >= :startRow"; // 최종 결과에서 시작 행 이후의 레코드를 선택
Query query = entityManager.createNativeQuery(paginatedSql, EnrollmentRecord.class);
query.setParameter("coursePattern", courseNumPattern);
query.setParameter("startRow", startRow);
query.setParameter("endRow", endRow);
return query.getResultList();
}
}
주의사항 및 흔히 발생하는 오류
네이티브 SQL 쿼리를 사용할 때 발생할 수 있는 일반적인 문제점과 그 해결책은 다음과 같습니다.
java.sql.SQLSyntaxErrorException: ORA-01747: invalid user.table.column, table.column, or column specification원인: SQL 쿼리 내의 필드 이름이나 테이블 이름이 데이터베이스의 예약어와 충돌하거나, 잘못된 형식으로 지정된 경우 발생합니다. 예를 들어, 'LIKE'와 같은 SQL 키워드를 컬럼 ALIAS로 사용하거나, 컬럼명에 특수 문자가 포함되었지만 적절하게 인용되지 않은 경우 발생할 수 있습니다.
해결: SQL 쿼리의 모든 테이블 및 컬럼 이름을 다시 확인하고, 예약어를 사용하지 않았는지, 또는 필요에 따라 적절한 인용 부호(예:
"COLUMN_NAME")로 감싸 주었는지 검토해야 합니다.javax.persistence.PersistenceException: org.hibernate.MappingException: Unknown entity: com.example.EnrollmentRecord원인: JPA
EntityManager가 해당 클래스를 엔티티로 인식하지 못하는 경우입니다. 가장 흔하게는 엔티티 클래스에@Entity어노테이션이 누락되었을 때 발생합니다.해결: 엔티티 클래스(예:
EnrollmentRecord)에@Entity어노테이션을 추가하여 JPA에게 이 클래스가 관리 대상 엔티티임을 알립니다.Caused by: org.hibernate.AnnotationException: No identifier specified for entity: com.example.EnrollmentRecord원인: 엔티티 클래스에 기본 키(Primary Key)가 정의되지 않은 경우 발생합니다. JPA는 각 엔티티 인스턴스를 데이터베이스에서 고유하게 식별할 수 있는 기본 키 필드를 요구합니다.
해결: 엔티티 클래스 내의 기본 키로 사용될 필드에
@Id어노테이션을 추가하여 기본 키임을 명시합니다.