Guide_Managing Transactions

Dev.Hammy·2023년 12월 17일
0

Spring Guides

목록 보기
16/46

이 가이드는 비침해적인 트랜잭션으로 데이터베이스 작업을 래핑하는 프로세스를 안내합니다.

Whay You Will Build

특별한 JDBC 코드를 작성할 필요 없이 트랜잭션 데이터베이스 작업을 수행하는 간단한 JDBC 애플리케이션을 구축합니다.

Starting with Spring Initializr

Create a Booking Service

먼저, BookingService 클래스를 사용하여 이름으로 시스템에 사람들을 예약하는 JDBC 기반 서비스를 만들어야 합니다. 다음 목록(src/main/java/guides/managingtransactions/BookingService.java)에서는 이를 수행하는 방법을 보여줍니다.

package guides.managingtransactions;

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;

import java.util.List;

@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"));
    }
    
}

코드에는 나머지 코드에 필요한 모든 데이터베이스 상호 작용을 수행하는 편리한 템플릿 클래스인 자동 연결 JdbcTemplate이 있습니다.

여러 사람을 예약할 수 있는 book method도 있습니다. 사람 목록을 반복하고 각 사람에 대해 JdbcTemplate을 사용하여 해당 사람을 BOOKINGS 테이블에 삽입합니다. 이 메서드에는 @Transactional 태그가 지정되어 있습니다. 이는 오류가 발생하면 전체 작업이 이전 상태로 롤백되고 원래 예외가 다시 발생함(re-throw)을 의미합니다. 즉, 한 사람이 추가되지 않으면 어떤 사람도 BOOKINGS에 추가되지 않습니다.

데이터베이스를 쿼리하는 findAllBookings 메서드도 있습니다. 데이터베이스에서 가져온(fetched) 각 행은 String로 변환되고 모든 행은 List으로 결합됩니다.

애플리케이션 구축

Spring 초기화는 애플리케이션 클래스를 제공합니다. 이 경우 이 애플리케이션 클래스를 수정할 필요가 없습니다. 다음 목록(src/main/java/guides/managingtransactions/ManagingTransactionsApplication.java에서)은 애플리케이션 클래스를 보여줍니다.

package guides.managingtransactions;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ManagingTransactionsApplication {

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

}

귀하의 애플리케이션에는 실제로 구성이 없습니다. Spring Boot는 classpath에서 spring-jdbch2를 감지하고 자동으로 DataSourceJdbcTemplate을 생성합니다. 이제 이 인프라를 사용할 수 있고 전용 구성이 없기 때문에 DataSourceTransactionManager도 생성됩니다. 이는 @Transactional로 주석이 달린 메서드(예: BookingServicebook 메서드)를 가로채는 구성 요소입니다. BookingService는 클래스 경로 검색을 통해 감지됩니다.

이 가이드에서 설명하는 또 다른 Spring Boot 기능은 시작 시 스키마를 초기화하는 기능입니다. 다음 파일(src/main/resources/schema.sql)은 데이터베이스 스키마를 정의합니다.

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

BookingService를 주입하고 다양한 트랜잭션 사용 사례를 보여주는 CommandLineRunner도 있습니다. 다음 목록(src/main/java/guides/managingtransactions/AppRunner.java)은 명령줄 실행기를 보여줍니다.

package guides.managingtransactions;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

@Component
public class AppRunner implements CommandLineRunner {

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

    private final BookingService bookingService;

    public AppRunner(BookingService bookingService) {
        this.bookingService = bookingService;
    }

    @Override
    public void run(String... args) throws Exception {
        bookingService.book("Alice", "Bob", "Carol");
        Assert.isTrue(bookingService.findAllBookings().size() == 3, "First booking should work with no problem");
        logger.info("Alice, Bob and Carol have been booked");

        try {
            bookingService.book("Chris", "Samuel");
        } catch (RuntimeException e) {
            logger.info("v--- The following exception is expect because 'Samuel' is too "+"big for the DB ---v");
            logger.error(e.getMessage());
        }

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

        logger.info("You shouldn't see Chris or Samuel. Samuel violated DB constraints. " + "and Chris was rolled back in the same TX");
        Assert.isTrue(bookingService.findAllBookings().size()==3, "'Samuel' should have triggered a rollback");

        try {
            bookingService.book("Buddy", null);
        } catch (RuntimeException e) {
            logger.info("v--- The following exception is except because null is not "+"valid for the DB ---v");
            logger.error(e.getMessage());
        }

        for (String person : bookingService.findAllBookings()) {
            logger.info("So far, " + person + "is booked");
        }
        logger.info("You shouldn't see Buddy or null. null violated DB constraints, and " + "Buddy was rolled back in the same TX");
        Assert.isTrue(bookingService.findAllBookings().size() == 3, "'null' should have triggered a rollback");
    }

}

BOOKINGS 테이블에는 first_name 열에 대한 두 가지 제약 조건이 있습니다.

  • 이름은 5자를 초과할 수 없습니다.
  • 이름은 null일 수 없습니다.

삽입된 처음 세 이름은 Alice, BobCarol입니다. 애플리케이션에서는 해당 테이블에 세 사람이 추가되었다고 주장(Assert.isTrue(...))합니다. 이것이 작동하지 않았다면 애플리케이션이 일찍 종료되었을 것입니다.

다음으로 ChrisSamuel에 대한 또 다른 예약이 이루어집니다. Samuel의 이름은 의도적으로 너무 길어서 삽입 오류가 발생했습니다. 트랜잭션 동작은 ChrisSamuel(즉, 이 트랜잭션의 모든 값)이 모두 롤백되어야 함을 규정합니다. 따라서 해당 테이블에는 여전히 3명의 사람만 있어야 하며, 이는 주장(assertion)이 보여줍니다.

마지막으로 Buddynull이 예약되었습니다. 출력에 표시된 대로 null로 인해 롤백도 발생하여 동일한 3명이 예약됩니다.

0개의 댓글