티스토리 뷰

728x90
반응형

데이터베이스에 데이터 CRUD 연산을 요청할 때 SQL 쿼리를 작성하여 실행합니다. 때때로 테이블의 제약조건으로 인해, SQL 쿼리 실행이 실패되기도 합니다. 단순히 하나의 SQL 쿼리 실행이 실패한 경우는 문제가 없습니다. 하지만, 여러 건의 데이터를 처리하는 쿼리가 실행되던 중 오류가 발생하는 경우도 찾아옵니다. 이러한 경우, 오류가 발생하기 이전에 완료된 작업은 데이터베이스에 저장할 것인지, 작업 전체를 오류로 판단하여 작업내용을 원복 할지 처리해야 합니다. 트랜잭션이란 데이터베이스에서 SQL을 실행하는 작업 단위를 뜻합니다. 스프링 부트에서는 데이터베이스의 트랜잭션을 처리할 수 있는 기능을 지원합니다. 이번 포스팅에서는 Springboot에서 Database의 Transaction을 관리하는 방법에 대해 알아보겠습니다.

Springboot Database 연동하는 프로젝트 다운로드

https://start.spring.io/ 에서 Springboot를 실행할 수 있는 프로젝트 환경을 설정한 뒤, 디펜던시 항목에 H2 Database, Spring Data JDBC를 추가하여 프로젝트를 다운로드합니다.

Springboot Database 연동 프로젝트 다운로드

다운로드한 프로젝트를 압축 해제한 후, build.gradle 파일을 열어보면 디펜던시가 추가된 것을 확인할 수 있습니다.

implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
runtimeOnly 'com.h2database:h2'

Springboot JDBCTemplate를 이용한 Database 연동하기

데이터베이스 트랜잭션을 관리하는 실습은 스프링 부트에서 제공하는 JDBC Template, H2 Database을 활용합니다. 스프링 부트에서 JDBC Template를 이용하여 관계형 데이터베이스와 연동하는 방법에 대해 기억이 잘 나지 않으신 분은 아래 포스팅 참조 바랍니다.

 

[Java] SpringBoot에서 관계형 Database에 접속 및 제어하는 법(JDBC template)

JDBC는 Java Database Connectivity이며, Java에서 Database에 접속하고 제어하기 위해 사용되는 API입니다. JDBC Template는 JDBC API를 사용하기 위해 Spring에서 제공하는 라이브러리 입니다. JDBC Template를..

shcheon.tistory.com

Database 연동하는 예제로 예약자의 이름을 데이터베이스의 Bookings라는 테이블에 저장하는 서비스 코드를 작성해보겠습니다. src/main/resources 경로에 schema.sql 파일을 생성하고 BOOKINGS 테이블을 생성하는 DDL을 작성합니다. resource 경로에 schema.sql은 스프링 부트가 시작되고 H2 Database를 초기화할 때, 읽어 들이는 파일이므로, 스프링 부트를 실행하면 자동으로 BOOKING 테이블이 생성됩니다.

drop table BOOKINGS if exists;
create table BOOKINGS(ID serial, FIRST_NAME varchar(5) NOT NULL);

BOOKINGS 테이블의 FRIST_NAME 컬럼은 varchar 타입이고, 최대 5 Bytes를 저장 가능하며 NULL값을 가질 수 없는 제약을 추가하였습니다. 다음으로 BOOKINGS 테이블에 데이터를 저장하는 Service 클래스를 작성하겠습니다.

package com.example.demo;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class BookingService {

    private final static Logger logger = LoggerFactory.getLogger(BookingService.class);

    private final JdbcTemplate jdbcTemplate;

    public BookingService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Transactional
    public void book(String... persons) {
        for (String person : persons) {
            logger.info("Booking " + person + " in a seat...");
            jdbcTemplate.update("insert into BOOKINGS(FIRST_NAME) values (?)", person);
        }
    }

    public List<String> findAllBookings() {
        return jdbcTemplate.query("select FIRST_NAME from BOOKINGS",
                (rs, rowNum) -> rs.getString("FIRST_NAME"));
    }

}

BookingService클래스에 @Component 어노테이션을 추가하여 스프링 빈 클래스로 추가해줍니다. book() 메서드는 JDBC Template을 이용하여 insert 쿼리를 실행합니다. book() 메서드 위에 추가된 @Transactional은 데이터베이스의 트랜잭션 관리를 수행하는 어노테이션입니다. book() 메서드에서 SQL 쿼리 실행 중 실패가 발생하면, 메서드 내에서 실행되었던 쿼리 내용은 모두 Rollback(=원상복귀) 됩니다. @Transactional 어노테이션 추가만으로, 데이터베이스의 트랜잭션을 손쉽게 관리할 수 있습니다. 스프링 부트의 트랜잭션 관리가 올바르게 동작하는지 CommandLineRunner를 통해 확인해보겠습니다.

package com.example.demo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {
    private final static Logger logger = LoggerFactory.getLogger(DemoApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public CommandLineRunner run(BookingService bookingService) {
        return (args) -> {
            bookingService.book("Alice", "Bob", "Carol");
            try {
                bookingService.book("Chris", "Samuel");
            } catch (RuntimeException e) {
                logger.error(e.getMessage());
            }

            for (String person : bookingService.findAllBookings()) {
                logger.info("So far, " + person + " is booked.");
            }

            try {
                bookingService.book("Guest", null);
            } catch (RuntimeException e) {
                logger.error(e.getMessage());
            }

            for (String person : bookingService.findAllBookings()) {
                logger.info("So far, " + person + " is booked.");
            }

        };

    }
}

CommandLineRunner를 스프링 컨텍스트 로드가 완료된 후 run() 메서드를 실행합니다. BookingService를 Argument로 추가하였는데, 이는 스프링 부트의 DI(=Dependency Injection)로 인해 자동으로 스프링 빈 객체가 주입됩니다. 스프링 부트를 실행하면, Alice, Bob, Carol은 정상적으로 BOOKINGS테이블에 저장되지만, Chris, Samuel은 varchar(5) 길이 제약, Guest, null 데이터는 NOT NULL제약으로 인해 트랜잭션이 롤백이 됩니다. Chris와 Guest는 정상적으로 쿼리 실행이 완료되었지만, 트랜잭션 오류 발생으로 인해 롤백되어 테이블에 저장이 되지 않은 것이죠. 

Booking Alice in a seat...
Booking Bob in a seat...
Booking Carol in a seat...
Booking Chris in a seat...
Booking Samuel in a seat...
// DB 삽입 오류
PreparedStatementCallback; SQL [insert into BOOKINGS(FIRST_NAME) values (?)]; Value too long for column "FIRST_NAME CHARACTER VARYING(5)": "'Samuel' (6)"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [22001-212]; nested exception is org.h2.jdbc.JdbcSQLDataException: Value too long for column "FIRST_NAME CHARACTER VARYING(5)": "'Samuel' (6)"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [22001-212]

So far, Alice is booked.
So far, Bob is booked.
So far, Carol is booked.
Booking Guest in a seat...
Booking null in a seat...
// DB not null 삽입 오류
PreparedStatementCallback; SQL [insert into BOOKINGS(FIRST_NAME) values (?)]; NULL not allowed for column "FIRST_NAME"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [23502-212]; nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "FIRST_NAME"; SQL statement:
insert into BOOKINGS(FIRST_NAME) values (?) [23502-212]

So far, Alice is booked.
So far, Bob is booked.
So far, Carol is booked.

지금까지 스프링 부트에서 데이터베이스의 트랜잭션을 관리하는 방법에 대해 알아보았습니다.

728x90
반응형
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함