당니의 개발자 스토리

Spring이 JDBC와 트랜잭션 작업을 줄여주는 방식 본문

Java, Spring

Spring이 JDBC와 트랜잭션 작업을 줄여주는 방식

clainy 2026. 5. 6. 11:28

이전 글에서는 프레임워크와 라이브러리의 차이, 그리고 IoC에 대해 정리했다.

 

이번 글에서는 Spring이 실제 DB에서 반복 코드를 어떻게 줄여주는지 정리해보려고 한다!!

 

Spring을 처음 배우면

스프링은 개발자가 비즈니스 로직에 집중할 수 있게 해준다

 

라는 말을 많이 들어봤을 것이다..

이게 뭔 말이지 싶은데,

JDBC 트랜잭션을 보면 더 잘 이해할 수 있다.

 

자바 프로그램에서 데이터를 저장하거나 조회하려면 DB와 연결해야 한다.

이때 기본적으로 사용하는 기술이 JDBC다.

 

JDBCJava Database Connectivity의 약자이고

자바에서 데이터베이스에 연결하기 위한 기본 방법이다.

 

JDBC를 사용하면 DB에 연결되니까

자바 코드에서 SQL을 실행할 수 있게 된다!!

예를 들어 회원 목록을 조회하거나, 게시글을 저장하거나, 주문 데이터를 수정할 수 있다.

 

JDBC를 직접 사용하면 대략 이런 코드가 필요하다.

Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/testdb",
    "root",
    "password"
);

PreparedStatement ps = conn.prepareStatement("SELECT * FROM user");

ResultSet rs = ps.executeQuery();

while (rs.next()) {
    String name = rs.getString("name");
    System.out.println(name);
}

rs.close();
ps.close();
conn.close();

 

딱 봐도 복잡하고 불편하다..

여기에서 Connection은 DB와 연결된 통로라고 보면 된다.

자바 프로그램이 DB에 접근하려면 먼저 이 연결 통로를 열어야 한다.

 

PreparedStatement는 SQL을 실행하기 위해 준비된 객체이고,

SELECT * FROM user 같은 SQL문을 DB에 보내기 위해 사용한다.

 

ResultSet 안에는 SQL 실행 결과가 들어간다.

예를 들어 user 테이블에서 조회된 여러 행의 데이터가 ResultSet에 담기고,

rs.next()를 통해 한 줄씩 꺼내서 읽을 수 있다.

 

마지막에 close()를 호출하는 것도 중요하다.

DB 연결은 무한히 만들 수 있는 것이 아니다.

열어놓고 닫지 않으면 연결이 계속 쌓이고, 결국 사용할 수 있는 DB 연결이 부족해질 수 있다.

이런 문제를 connection leak이라고 부르기도 한다.

 

즉, JDBC를 직접 사용하면 개발자가 직접 챙겨야 하는 일이 많다.

DB 연결을 열고, SQL을 준비하고, SQL을 실행하고, 결과를 꺼내고, 사용한 자원을 닫아야 한다.

여기에 예외 처리까지 직접 작성해야 한다.

 

문제는 이런 흐름이 DB 작업을 할 때마다 반복된다는 것이다!!!!

 

Spring을 몰랐을 때는 개발자가 DB 관리, 트랜잭션 처리, JDBC 코드 작성, 예외 관리까지 직접 챙겨야 했다.

반대로 Spring을 사용하면 DB 관리, 선언적 트랜잭션 처리, 템플릿 제공, 범용 예외 처리 같은 부분을 Spring이 도와준다.


트랜잭션이란?

트랜잭션은 여러 작업을 하나의 작업처럼 묶는 것이다.

가장 쉬운 예시는 계좌이체다.

 

A가 B에게 10,000원을 보내는 상황을 생각해보자.

이 과정은 사실 두 단계다.

  1. A 계좌에서 10,000원 차감
  2. B 계좌에 10,000원 추가

이 두 작업은 반드시 둘 다 성공해야 한다.

 

만약 1번은 성공했는데 2번에서 오류가 나면 문제가 생긴다.

A의 돈은 빠져나갔는데 B는 돈을 받지 못한 상태가 된다.

이건 정상적인 계좌이체라고 볼 수 없다.

 

그래서 중간에 실패하면 이전 작업까지 되돌려야 한다.

실패했을 때 이전 상태로 되돌리는 것을 rollback이라고 한다.

반대로 모든 작업이 성공해서 최종 반영하는 것을 commit이라고 한다.

 

트랜잭션은 이런 commit과 rollback을 관리하는 개념이다.

즉, 여러 DB 작업이 모두 성공해야만 최종 반영하고,

하나라도 실패하면 전체를 취소하는 방식이다.

 

Spring 없이, JDBC만 사용할 때 트랜잭션을 직접 처리하려면 이런 코드가 필요하다.

Connection conn = null;

try {
    conn = DriverManager.getConnection(url, user, password);

    conn.setAutoCommit(false);

    // 1. A 계좌에서 돈 차감
    // 2. B 계좌에 돈 추가

    conn.commit();
} catch (Exception e) {
    if (conn != null) {
        conn.rollback();
    }
} finally {
    if (conn != null) {
        conn.close();
    }
}

여기서 setAutoCommit(false)는 자동으로 DB에 반영하지 말라는 뜻이다.

작업을 모두 수행한 뒤 성공하면 commit()을 호출한다.

중간에 예외가 발생하면 rollback()을 호출한다.

마지막에는 connection도 닫아야 한다.

 

역시 이렇게 하면 동작은 하지만 매번 작성하기 번거롭다.

commit을 빼먹을 수도 있고, rollback 처리를 잘못할 수도 있고, connection을 닫지 않을 수도 있다.

 

그리고 이런 코드가 여러 서비스 로직에 계속 반복되면 코드가 지저분해진다.

트랜잭션 처리 자체가 핵심 기능보다 더 크게 보이는 상황이 생길 수 있다.

 

그런데 우리의 스프링..

Spring을 사용하면 이 과정을 훨씬 간단하게 처리할 수 있다!!!

 

대표적인 방식이 @Transactional이다.

@Transactional
public void transfer() {
    // 1. A 계좌에서 돈 차감
    // 2. B 계좌에 돈 추가
}

메서드 위에 @Transactional을 붙이면 Spring이 트랜잭션을 대신 관리한다.

 

메서드가 정상적으로 끝나면 commit한다.

중간에 예외가 발생하면 rollback한다.

 

개발자는 commit, rollback 코드를 직접 반복해서 작성하지 않아도 된다.

 

이런 방식을 선언적 트랜잭션 처리라고 한다.

 

선언적이라는 말은 직접 세부 코드를 작성하는 대신,

어노테이션이나 설정으로 의도를 표현한다는 뜻이다.

 

즉, 개발자는 '이 메서드는 트랜잭션으로 처리해줘' 라고 선언한다.

실제 트랜잭션 시작, commit, rollback 처리는 Spring이 맡는다.

 

그래서 JDBC를 직접 사용하면 DB 연결 열기, SQL 준비하기, SQL 실행하기, 결과 꺼내기, 자원 닫기, 예외 처리하기가 계속 반복된다.

 

Spring은 이런 반복 흐름을 JdbcTemplate으로 줄여준다.

JdbcTemplate을 사용하면 개발자는 SQL과 결과 처리에 더 집중할 수 있다.

String sql = "SELECT name FROM user WHERE id = ?";

String name = jdbcTemplate.queryForObject(
    sql,
    String.class,
    1
);

이 코드에서는 DB 연결을 열고 닫는 코드가 보이지 않는다.

그렇다고 연결이 안 일어나는 것은 아니다.

Spring과 JdbcTemplate이 뒤에서 처리해주는 것이다.

 

개발자는 어떤 SQL을 실행할지, 결과를 어떤 타입으로 받을지에 집중하면 된다.

JdbcTemplate은 JDBC를 아예 없애는 기술이 아니다.

JDBC의 반복 코드를 줄여주는 도구에 가깝다.

 

Connection을 열고 닫는 일, 예외를 처리하는 일처럼 반복되는 부분은 Spring이 맡는다.

개발자는 SQL과 결과 매핑처럼 실제로 필요한 부분을 작성한다.


다음은 예외 처리다.

JDBC를 직접 사용하면 SQLException 같은 예외를 자주 만나게 된다.

 

SQLException은 DB 작업 중 문제가 생겼을 때 발생하는 예외다.

예를 들어 SQL 문법이 틀렸거나, DB 연결에 실패했거나, 제약 조건을 위반했을 때 발생할 수 있다.

 

문제는 DB 기술이나 드라이버마다 예외의 세부 형태가 다를 수 있다는 점이다.

MySQL을 사용할 때와 Oracle을 사용할 때 세부 예외가 다르게 나타날 수 있다.

 

그러면 개발자가 DB 기술별 예외를 직접 신경 써야 하는 상황이 생긴다.

 

코드가 특정 DB 기술에 강하게 묶일 수 있다.

Spring은 DB 관련 예외를 DataAccessException이라는 계층으로 추상화해서 제공한다.

 

여기서 추상화는 복잡한 내부 차이를 숨기고,

사용하는 쪽에서는 단순하고 일관된 방식으로 접근할 수 있게 해주는 것이다.

 

예를 들어 MySQL을 쓰든, Oracle을 쓰든, JDBC를 쓰든, JPA를 쓰든

DB 접근 중 발생하는 예외를 Spring 방식으로 일관되게 다룰 수 있다.

 

이렇게 하면 특정 DB 기술에 너무 강하게 묶이지 않고 코드를 작성할 수 있다.


Spring이 DB 작업에서 도와주는 부분은 크게 네 가지다.

 

1. DB 연결과 자원 관리를 도와준다.
2. 트랜잭션 처리를 @Transactional로 간단하게 만든다.
3. JdbcTemplate 같은 템플릿으로 반복 코드를 줄인다.
4. DB 예외를 일관된 방식으로 처리할 수 있게 해준다.

 

물론!! Spring을 쓰지 않아도 JDBC만으로 프로그램을 만들 수 있다.

하지만 모든 과정을 직접 챙겨야 한다.

 

DB 연결을 직접 열고 닫아야 하고, 트랜잭션을 직접 commit, rollback 해야 한다.

예외도 직접 처리해야 하고, 반복되는 JDBC 코드도 계속 작성해야 한다.

 

Spring을 사용하면 이런 복잡하고귀찮은 작업들을 Spring 프레임워크가 대신 해준다...

 

그래서 개발자는 어떤 기능을 만들지, 어떤 순서로 동작해야 하는지, 실패했을 때 어떻게 처리해야 하는지에 더 집중할 수 있다.

 

하지만 여전히 개발자는 DB 구조를 이해해야 하고,

트랜잭션이 필요한 상황도 판단해야 하며, 예외 상황도 고려해야 할 수 있어야 된다고 생각한다!