1. JDBC (Java Database Connectivity)
초기 방식 (1997~)
SQL을 직접 작성해야 하고, 커넥션/리소스를 직접 관리해야 함
→ 귀찮고 반복되는 코드 많음
특징
• DriverManager를 통해 DB 연결
• Statement 또는 PreparedStatement로 SQL 실행
• ResultSet으로 결과 조회
• 직접 Connection, Statement, ResultSet을 닫아줘야 함 (리소스 누수 위험)
단점
• 반복 코드가 많음 (try-catch-finally로 리소스 정리 필수)
• SQL과 비즈니스 로직이 뒤섞임
• 트랜잭션 관리 어려움
💻 예제 (JDBC)
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");
pstmt = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
pstmt.setString(1, "spring");
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name"));
}
} finally {
// 리소스 정리 (반드시 닫아야 함)
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
}
2. JDBC Template (Spring 지원)
JDBC의 불편한 점을 해결한 스프링 기술 (2003~)
SQL은 직접 써야 하지만, 반복 코드(리소스 관리 등)를 없애줌
→ 좀 더 편리하게 JDBC 사용 가능
특징
• JDBC의 리소스 정리를 자동으로 처리
• SQL은 여전히 직접 작성해야 함 (SQL 중심 개발)
• 코드가 짧고 가독성이 좋아짐
💻 예제 (JDBC Template)
@Autowired
private JdbcTemplate jdbcTemplate;
public List<Member> findByName(String name) {
return jdbcTemplate.query("SELECT * FROM users WHERE name = ?",
new Object[]{name},
(rs, rowNum) -> new Member(rs.getLong("id"), rs.getString("name")));
}
JDBC보다 훨씬 간결해짐
단점
• 여전히 SQL을 직접 작성해야 함
• 객체를 직접 매핑해야 함 (RowMapper 필요)
3. JPA (Java Persistence API)
SQL 대신 객체 중심으로 DB를 다룰 수 있도록 만든 표준 기술 (2006~)
SQL을 직접 안 써도 되고, 객체 중심으로 데이터 처리 가능
→ 객체를 데이터베이스 테이블처럼 다루는 기술
JPA(Java Persistence API)는 말 그대로 API(인터페이스)이다.
즉, 데이터베이스와 객체 간의 매핑을 정의하는 표준 스펙이고, 이걸 직접 구현한 건 아님.
JPA를 쓰려면 JPA를 구현한 라이브러리(구현체)가 필요.
JPA의 대표적인 구현체
1. Hibernate (가장 많이 사용됨)
• JPA의 대표적인 구현체
• Spring Boot의 기본 JPA 구현체로 포함되어 있음
• 대부분의 JPA를 쓴다면 Hibernate를 같이 씀
2. EclipseLink
• Oracle에서 만든 JPA 구현체
• Spring에서 잘 사용하지 않음 (주로 Oracle 기반 프로젝트에서 사용)
3. OpenJPA
• Apache에서 만든 구현체지만 거의 안 씀
JPA 자체로는 아무 기능도 안 한다.
만약 spring-boot-starter-data-jpa를 추가하면, Hibernate를 기본 구현체로 사용해서 동작하게 됨.
특징
• SQL을 직접 작성하지 않아도 됨 (자동 생성)
• 객체(Entity)와 DB 테이블을 자동 매핑
• 트랜잭션 관리 편리 (@Transactional)
• DB에 맞춰 쿼리를 변경할 필요 없이, 객체 기반으로 개발 가능
예제 (JPA)
@Entity
public class Member {
//오라클에서 오토인크리먼트 같은 설정
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Transactional
public class JpaMemberRepository implements MemberRepository{
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member member) {
em.persist(member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class,id);
return Optional.ofNullable(member);
}
단점
• SQL을 완전히 컨트롤하기 어렵다 (복잡한 쿼리는 @Query 필요)
• 학습 비용이 있음 (기본 동작 원리 이해 필요)
⭐️ 4. Spring Data JPA
JPA를 더 쉽게 사용할 수 있도록 만든 스프링 기술 (2012~)
JPA의 단순 반복 코드를 줄이고, 인터페이스만 만들면 자동 구현
→ JPA보다 더 간결하고 편리
특징
• 리포지토리 인터페이스만 선언하면 자동으로 구현
• 쿼리 메서드 기능 제공 (findByName, findByEmail)
• 페이징 처리 기능 제공 (Pageable)
💻 예제 (Spring Data JPA)
public interface MemberRepository {
Member save(Member member);
Optional<Member> findById(Long id);
Optional<Member> findByName(String name);
List<Member> findAll();
}
//interface
import domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
//스프링이 자동으로 구현체 만들어서 빈으로 등록해줌
public interface SpringDataJpaMemberRepository extends JpaRepository<Member,Long>, MemberRepository {
@Override
Optional<Member> findByName(String name);
}
* JpaRepository에 기본적인 공통 CRUD가 구현되어 있음.
JPQL select m from Member m where m.name = ?
인터페이스 이름을 규칙에 따라 만들면 Reflection 기술을 통해 쿼리로 바꿔줌. (e.g. findByNameAndId)
//spring config class
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
구현 클래스 없이도 알아서 동작
memberRepository.findByName("spring");
SQL 없이도 DB에서 조회 가능
단점
• 복잡한 쿼리는 @Query로 직접 작성해야 함
• JPA 동작 원리를 모르면 “왜 이게 이렇게 동작하지?” 하는 상황이 생길 수 있음(추상화로 인해)
정리
결론
• SQL을 직접 제어해야 하면 JDBC / JDBC Template
• 객체 중심 개발을 원하면 JPA / Spring Data JPA
• 가장 편한 건 Spring Data JPA (인터페이스만 만들면 끝이라 현대 백엔드 개발에서 많이 사용되는 중)
의문점 🤔
JPA를 사용하면 직접 쿼리짜는거보다 성능면에서 비효율적인 상황이 생기지 않을까?
대규모 서비스의 경우 다중 조인이 들어가는 쿼리가 엄청 복잡한 경우도 많기 때문이다.
JPA가 비효율적인 경우
1. 복잡한 SQL 최적화가 필요한 경우
• JPA는 기본적으로 객체와 테이블을 매핑해서 관리하는데, 이 과정에서 불필요한 쿼리가 나갈 수도 있음.
• 특히 JOIN이 많거나 특정 조건으로 데이터를 효율적으로 가져와야 하는 경우, JPA보다는 직접 SQL을 짜는 게 훨씬 나음.
e.g. select문만 해도 컬럼 하나만 필요한데 객체 자체를 매핑해서 가져오면 컬럼 다 가져오는것
2. N+1 문제 발생
• 연관된 데이터를 가져올 때, 예상치 못한 추가 쿼리가 발생할 수 있음. (바로 조인 걸어서 쿼리 날리는거 대비)
• 예를 들어, “회원과 회원이 작성한 게시글 목록을 가져오기” 할 때, 회원 1명 조회하는 쿼리 + 회원별 게시글을 개별 조회하는 쿼리(N개) 발생 가능.
• 해결법: @BatchSize, fetch join, EntityGraph 등을 써야 함.
3. 대량 데이터 처리
• JPA는 기본적으로 엔티티 객체를 관리하는 방식이라, 데이터가 많으면 메모리를 많이 씀.
• 대량 데이터 업데이트(UPDATE users SET ... WHERE ...)나 대량 삭제(DELETE FROM users WHERE ...) 같은 작업은
JPA보다 네이티브 SQL이 훨씬 빠름.
• 해결법: @Modifying @Query나 EntityManager.createNativeQuery() 사용.
4. DB 튜닝이 중요한 경우
• 대기업 서비스는 쿼리 성능 튜닝이 필수적인데, JPA는 SQL을 자동 생성하니까 세밀한 튜닝이 어려움.
• EXPLAIN 분석, 인덱스 활용, 파티셔닝 같은 DB 최적화 작업이 필요한 경우, 직접 SQL을 작성하는 게 유리함.
그래서 해결 방법은?
대부분 JPA + QueryDSL + 네이티브 SQL 조합을 씀.
1. 기본적인 CRUD → JPA & Spring Data JPA 사용
• 단순한 insert, select, update 같은 건 JPA로 처리.
• findByName(), findByEmail() 같은 간단한 쿼리는 Spring Data JPA로 해결.
2. 복잡한 조회 쿼리 → QueryDSL 사용
• QueryDSL은 JPA 기반이면서도 SQL처럼 자유롭게 쿼리를 작성할 수 있음.
• JPQL보다 가독성이 좋고, 동적 쿼리 작성이 쉬움.
3. 대량 데이터 처리 & 성능 최적화 → 네이티브 SQL 사용
• @Query(value = "SELECT * FROM users WHERE ...", nativeQuery = true)
• EntityManager.createNativeQuery()
• JDBC Template 활용해서 직접 SQL 실행.
정리
• JPA는 편리하지만, 성능 문제를 일으킬 수 있음
• 복잡한 조회, 대량 데이터 처리, 최적화가 필요한 경우 SQL이 더 좋음
💵 금융권, 증권계에서는 어떨까?
증권 서비스 같은 고성능, 대규모 데이터 처리가 중요한 시스템에서는
JPA를 사용하기보다는 순수 SQL 쿼리를 사용하는 것이 더 적합할 수 있음
1. 쿼리 최적화 필요
• 증권 서비스처럼 대량의 데이터를 다루는 시스템에서는 SQL 성능이 매우 중요해.
• JPA는 객체와 데이터베이스를 매핑할 때 추가적인 오버헤드가 발생할 수 있기 때문에, 복잡한 쿼리나 성능 최적화가 필요한 상황에서는 순수 SQL을 쓰는 것이 유리할 수 있어.
• SQL 튜닝을 통해 데이터베이스 성능을 최적화할 수 있기 때문에, 디버깅과 튜닝이 가능하고, 이를 통해 쿼리 성능을 세밀하게 조정할 수 있어.
2. JPA의 추상화된 쿼리
• JPA는 객체 지향적인 접근을 제공하지만, 복잡한 쿼리에서는 자동으로 생성되는 SQL이 최적화되지 않을 수 있어.
• 예를 들어, JPA에서 생성된 쿼리가 예상보다 비효율적이거나 인덱스를 잘못 활용하는 경우가 발생할 수 있음.
• 반면, 순수 SQL을 사용하면 원하는 방식으로 인덱스 활용, 조인 최적화, 하위 쿼리 튜닝 등 성능을 극대화할 수 있어.
3. N+1 문제 등 성능 이슈
• N+1 문제와 같은 성능 이슈가 있을 때, JPA는 자동으로 이를 해결할 수 있는 기능을 제공하지만, 경우에 따라 직접 쿼리를 작성하는 것이 성능 면에서 훨씬 유리할 수 있어.
• 직접 쿼리로 필요한 데이터를 한 번에 가져오는 방식으로 효율적으로 최적화할 수 있음.
4. 복잡한 트랜잭션 처리
• 증권 서비스에서는 고도의 트랜잭션 처리와 원자성이 중요해. JPA는 트랜잭션 처리를 제공하지만, 복잡한 비즈니스 로직에서는 수동 트랜잭션 관리가 필요한 경우가 많음.
• 순수 SQL을 사용하면 트랜잭션 관리를 세밀하게 제어할 수 있어, 예를 들어, 여러 쿼리를 한 번의 트랜잭션으로 묶거나, 대량의 데이터 처리를 효율적으로 할 수 있어.
5. DB 특화 기능 활용
• 오라클 DB 같은 특화된 DB 기능을 사용할 때, JPA는 그 기능을 완전히 활용하기 어려울 수 있음.
• 예를 들어, 오라클에서 제공하는 배치 처리, 파티셔닝, 병렬 처리 같은 고급 기능들을 JPA로 완벽히 활용하기 어렵고, 순수 SQL을 사용하면 이러한 DB의 특화된 기능을 최대한 활용할 수 있어.
DB 관련 기술을 알아보면서 역시 기술은 상황에 적합한 것을 선택하는 것이 최고구나..라는 것을 느끼게 되었다.
Spring data JPA가 그렇게 신세계라는 얘기를 듣고 찍먹해봤지만 결국 네이티브쿼리를 100% 대체하기는 힘들고,
편리함은 디버깅 용이성과 반비례 하기 때문이다. (만들 때 편했다가 유지보수 할 때 고통받을 수 있음)
결국은 네이티브 쿼리도 잘 해야 하고 상황에 따라 JPA와 같은 기술도 적절히 사용해야 한다는 교과서 적인 결론이 나왔다 ㅎ..