Spring 트랜잭션 관리

■ 참고 – Managing Transactions

■ 특별한 JDBC 코드를 작성하지 않고 데이터베이스 작업을 트랜잭션 처리하는 간단한 JDBC 응용 프로그램을 빌드한다. Eclipse에서 Maven 프로젝트를 생성한다.

Eclipse Maven Project 생성

■ Maven pom.xml 내용

<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0  http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.iotinfra</groupId>
  <artifactId>pilot.transaction</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

■ Spring Boot Maven 플러그인은 많은 편리한 기능을 제공한다.

  • 클래스 패스에 있는 모든 jar를 모아 하나의 실행가능한 “über-jar”를 빌드하므로 서비스를 실행하고 전송하는 것이 더 편리하다.(über-jar는 패키징시 제작된 모듈과 디펜던시가 하나의 jar에 파일에 포함된 것을 의미. https://opennote46.tistory.com/110)
  • public static void main () 메서드를 검색하여 실행 가능한 클래스로 플래그를 지정한다.
  • Spring Boot 의존성에 맞게 버전 번호를 설정하는 빌트인 의존성 분석기를 제공한다. 원하는 버전을 무시할 수 있지만 기본적으로 Boot에서 선택한 버전 세트가 사용된다.

■ BookingService.java – 예약(booking) 서비스 생성, 저 BookingService 클래스를 사용하여 사용자 이름으로 시스템에 사람들을 예약하는 JDBC 기반 서비스를 만든다.

package pilot.transaction.hello;

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

위의 코드에는 autowired JdbcTemplate이 있다. 이 템플릿은 아래 코드에서 필요한 모든 데이터베이스 상호 작용을 수행하는 편리한 템플릿 클래스다.


여러 사람을 예약하기위한 booking 방법은 사람 목록을 반복하며 각 사람에 대해 JdbcTemplate을 사용하여 BOOKINGS 테이블에 삽입한다. 이 메서드는 @Transactional로 태그로 지정된다. 즉, 모든 오류로 인해 전체 작업이 이전 상태로 롤백되고 원래 예외가 다시 throw된다. 이는 한 사람도 추가하지 않으면 어떤 사람도 BOOKINGS에 추가되지 않는다는 것을 의미한다.


또한 데이터베이스를 조회하는 findAllBookings 메소드가 있다. 데이터베이스에서 가져온 각 행을 String으로 변환 한 다음 List로 어셈블한다.

■ 위의 코드에서 JdbcTemplate can not resolved to a tyle 에러가 발생했다. 그래서 Maven Dependency를 확인했더니 spring-boot-starter, spring-boot-starter-jdbc에 jar 파일이 존재하지 않았다.

boot-starter-jdbc jar 누락에러 발생

해결은 윈도우 탐색기에서 로컬 Maven Repository의 spring-jdbc디렉토리를 지우고 다시 이클립스에서 현재 프로젝트에서 Maven > Update Project…를 실행하니 JDBCTemplate can not resolved…에러가 사라졌다. 그러나 spring-boot-starter, spring-boot-starter-jdbc에 jar파일은 여전이 존재하지 않는다.

■ 애플리케이션 빌드 – Application.java

package pilot.transaction.hello;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

■ @SpringBootApplication은 다음을 모두 추가하는 편리한 주석입니다.

  • @Configuration은 클래스를 애플리케이션 컨텍스트의 Bean 정의 소스로 태그 지정한다.
  • @EnableAutoConfiguration은 Spring Boot에게 클래스 패스 설정, 다른 bean 및 다양한 속성 설정을 기반으로 bean 추가를 시작하도록 지시한다.
  • 일반적으로 @EnableWebMvc를 Spring MVC app에 추가하지만, Spring Boot는 classpath에서 spring-webmvc를 볼 때 자동으로 추가한다. 이것은 응용 프로그램에 웹 응용 프로그램으로 플래그를 지정하고 DispatcherServlet 설정과 같은 주요 동작을 활성화합니다.
  • @ComponentScan은 Spring에게 hello 패키지의 다른 구성 요소, 구성 및 서비스를 찾아서 컨트롤러를 찾을 수있게 한다.

main() 메소드는 Spring Boot의 SpringApplication.run() 메소드를 사용하여 애플리케이션을 시작한다. 한 줄의 XML도 없고 web.xml 파일도 없다. 이 웹 응용 프로그램은 100 % 순수 자바이며 연관이나 인프라 구성에 대해 다룰 필요가 없다.

이 응용 프로그램은 실제로 설정 부분이 없다. Spring Boot는 classpath와 h2에서 spring-jdbc를 감지하고 자동으로 DataSource와 JdbcTemplate을 생성한다. 이러한 인프라가 현재 사용 가능하고 전용 구성이 없으므로 DataSourceTransactionManager가 만들어집니다.이 인스턴스는 @Transactional 주석이 달린 메소드 (예 : BookingService의 book)를 가로채는 구성 요소이다. BookingService는 클래스 경로 검색을 통해 탐지된다.

■ src/main/resources/schema.sql – 이 가이드에서 설명하는 또 다른 스프링 부트 기능은 시작할 때 스키마를 초기화하는 기능이다.

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

■ AppRunner.java – BookingService를 주입하고 다양한 트랜잭션 사용 사례를 보여주는 CommandLineRunner도 있다.

package pilot.transaction.hello;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
@Component
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 expect 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");
    }
}

■ 애플리케이션 빌드 

애플리케이션 빌드

■ 완성된 애플리케이션 파일 구조

애플리케이션 파일 구조

Gradle 또는 Maven을 사용하여 명령 줄에서 응용 프로그램을 실행할 수 있다. 또는 모든 필요한 종속성, 클래스 및 자원을 포함하는 단일 실행 가능 JAR 파일을 빌드하고 실행할 수 있다. 따라서 개발 수명주기, 다양한 환경에서 응용 프로그램으로 서비스를 쉽게 배포, 버전 및 배포 할 수 있다.

■ 빌드된 JAR 애플리케이션 실행 1

java -jar target/pilot.transaction-0.0.1-SNAPSHOT.jar

■ 빌드된 JAR 애플리케이션 실행 2 

■ Eclipse Console에서 실행중인 Spring Transaction 애플리케이션 로그를 확인할 수 있다.