Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
Backend/Spring

JDBC, JPA 자바 데이터베이스 연동 기술 변천사

by Toughie 2025. 2. 9.

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 @QueryEntityManager.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와 같은 기술도 적절히 사용해야 한다는 교과서 적인 결론이 나왔다 ㅎ..