[스프링부트 #5] 스프링 기반 DB 통신의 핵심
DB와 통신할 때는 쿼리 즉, 어떤 데이터를 가져올지 명령하는 SQL문과 DB에서 어떻게 데이터를 응답해주는지 결과 데이터 구조를 명확히 알고 있기위해서 정리하였습니다.
Aug 05, 2025
![[스프링부트 #5] 스프링 기반 DB 통신의 핵심](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog-custom%3Ftitle%3D%255B%25EC%258A%25A4%25ED%2594%2584%25EB%25A7%2581%25EB%25B6%2580%25ED%258A%25B8%2B%25235%255D%2B%25EC%258A%25A4%25ED%2594%2584%25EB%25A7%2581%2B%25EA%25B8%25B0%25EB%25B0%2598%2BDB%2B%25ED%2586%25B5%25EC%258B%25A0%25EC%259D%2598%2B%25ED%2595%25B5%25EC%258B%25AC%2B%26tag%3DTemplate%2B1%26description%3D%26template%3D3%26backgroundImage%3Dhttps%253A%252F%252Fsource.inblog.dev%252Fog_image%252Fdefault.png%26bgStartColor%3D%252323ec86%26bgEndColor%3D%252323ec86%26textColor%3D%2523000000%26tagColor%3D%2523000000%26descriptionColor%3D%2523000000%26logoUrl%3D%26blogTitle%3DGyeongwon%2527s%2Bblog&w=2048&q=75)
DB와 통신할 때는 쿼리 즉, 어떤 데이터를 가져올지 명령하는 SQL문과 DB에서 어떻게 데이터를 응답해주는지 결과 데이터 구조를 명확히 알고 있어야 한다.
1. JDBC 기반 통신 방식

- DB에서 결과를 가져오면
ResultSet
형태로 반환된다.
- 개발자가 직접 파싱해서 Java 객체(User 등)로 변환해야 한다. 요즘 기술력으로는 이 과정은 귀찮고 반복적이다.
- JDBC는 상대적으로 저수준이라고 볼 수 있다.
즉, 데이터 껍데기(ResultSet) 와 비즈니스 로직 객체(User) 사이를 직접 연결해야 한다.
- 첫 번째 파싱: DB에서 ResultSet 형태로 받아옴
- 두 번째 파싱: ResultSet → 자바 객체(User)로 수동 매핑
2. JPA
- JPA는 SQL 작성 없이 메서드 호출로 쿼리를 수행한다.
- DB 결과를 엔티티 객체에 자동으로 매핑해준다.
// 예: JPA는 아래와 같은 코드를 자동으로 처리해줌
User user = userRepository.findById(1L);
즉, JPA는 첫 번째 파싱(ResultSet) 과 두 번째 파싱(User 클래스 채우기) 까지 자동화한다.
- 첫 번째 파싱: JPA가 SQL 실행 → ResultSet을 내부적으로 처리
- 두 번째 파싱: ResultSet 데이터를 기반으로 User 객체 자동 생성 및 필드 자동 주입
JPA에서 Entity 클래스는 반드시 파라미터 없는 생성자가 있어야 한다.
Hibernate는 이 생성자를 통해 객체를 만든다 (리플렉션 기반이기 때문).
3. 조인(Join) 상황의 예외
- 조인 결과는 하나의 테이블이 아니라 여러 테이블이 합쳐진 새로운 구조이다.
- 이 구조는 기존 엔티티(User, Post 등)와 1:1로 매칭되지 않는다.
- 그래서 JPA는 조인된 결과를 엔티티에 매핑해주지 않는다.
Entity 는 반드시 실제 존재하는 DB 테이블과 필드가 정확히 일치해야 하며, 조인된 결과처럼 여러 테이블이 섞인 구조는 절대 엔티티로 만들 수 없다. 그런 구조는 DTO를 써서 직접 Object Mapping(OM) 해야 한다.
- 조인 상황에서는 직접 파싱해서 원하는 DTO로 만들어야 한다. 이를 Object Mapping(OM) 이라고 부른다.
public class UserPostDTO {
private String username;
private String postTitle;
}
- 이 DTO는 DB 테이블이 아니라 조인 결과를 담기 위한 커스텀 객체다.
- 엔티티(Entity) 는 반드시 DB 테이블과 구조가 1:1이어야 하고, 조인 결과를 담는 DTO는 엔티티가 아니다.
OM은 DB와 상관없이 객체끼리 데이터를 주고받는 구조, 단순 복사/변환 역할만 함 (DB는 신경 안 씀)
ORM은 객체를 DB에 저장하거나 불러오도록 자동화시켜주는 기술이다.
스프링에서는
ModelMapper
, MapStruct
등이 OM 도구고, JPA
, Hibernate
는 ORM 도구다.4. 핵심 요약
구분 | 설명 |
JDBC | 쿼리 + 파싱 모두 수작업 |
JPA | 쿼리 + 엔티티 매핑 자동 |
조인 결과 | JPA가 매핑 못함, 직접 DTO로 매핑 |
OM (Object Mapping) | 조인 결과를 DTO로 파싱하는 작업 |
ORM | JPA 포함 개념. 객체와 관계형 DB 매핑 전체를 포괄함 |
5. 실습 예제
Object[]
를 수동으로Entity
로 매핑하는 방법 (findAll
,findById
)Board.class
를 사용한 자동 매핑 방식 (findAllV2
,findByIdV2
)
Board.java
- Entity 클래스

항목 | 설명 |
@Entity | 이 클래스는 JPA가 관리하는 객체, 즉 DB 테이블과 매핑되는 클래스임 |
@Table(name = "board_tb") | 실제 매핑될 테이블명 지정 |
@Id , @GeneratedValue | PK이고, 자동 증가 전략( IDENTITY ) 사용 |
생성자 오버로딩 | 기본 생성자 + 필드를 직접 세팅하는 생성자 (DTO 역할도 가능) |
@Getter | Lombok으로 모든 필드의 Getter 자동 생성 |
BoardRepository.java
– 직접 EntityManager를 이용한 DB 접근

EntityManager
를 직접 주입 받아 JPQL / NativeQuery로 SQL 작성
@Repository
: 스프링 컴포넌트 스캔 대상 + 예외 변환 기능 자동 적용됨
@RequiredArgsConstructor
:final
필드인em
을 자동 생성자로 주입받음
메서드 분석 _ findAll() → 직접 매핑 방식

ativeQuery
: 직접 SQL 사용
- 리턴 결과는
List<Object[]>
형태
Object[]
의 순서를 정확히 알아야 함 → 매우 불편하고 타입 안정성 낮음
수작업으로 엔티티 객체로 수동 매핑
메서드 분석 _ findAllV2() → 자동 매핑 방식

- 두 번째 파라미터로
Board.class
전달 시, JPA가 자동으로 매핑
- 컬럼 이름 ↔ 필드 이름이 정확히 일치해야 함 (alias 주의)
- 깔끔하고 실무에서 더 선호됨
6. Spring Data JPA 환경에서 직접 작성한 Repository 메서드들을 검증 실습

@DataJpaTest
는 JPA 관련 컴포넌트만 로딩해서 테스트. 트랜잭션 자동 롤백됨 (DB 깨끗하게 유지), 기본적으로 Spring Data JPA 인터페이스 기반 리포지토리만 자동 등록됨
- 네가 만든
EntityManager
기반 커스텀 Repository는 수동 등록해야 한다 →@Import
필요

- 직접 SQL +
Object[]
→ 수동 매핑 방식
- 데이터가 잘 매핑되어 있는지
System.out.println
으로 확인
검증 포인트
- 결과가
List<Object[]>
였던 것을 잘 가공해Board
객체로 바뀌었는가?
- null, 캐스팅 오류가 없는가?

Object[]
기반 수동 매핑 방식
- 위와 동일하게 id 존재 여부에 따라 분기 처리
검증 포인트
- null 체크 + 수동 매핑의 정확성
Object[]
의 순서가 쿼리 컬럼 순서와 일치해야 한다는 점
이 테스트는 JPA 기반으로 직접 SQL을 작성하고 결과를 수동 또는 자동으로 매핑하는 방식에 대한 확실한 감 잡기 실습이며, JDBC와의 비교 이해, Spring Data JPA로 나아가기 위한 기반 지식이 모두 포함되어 있다.
Share article