[스프링 DB 1편] - 스프링과 문제 해결 - 트랜잭션(2)

조현수·2023년 1월 18일
0

스프링 DB 1편

목록 보기
7/11
post-thumbnail

이 글은 강의 : 김영한님의 - "[스프링 DB 1편 - 데이터 접근 핵심 원리]"을 듣고 정리한 내용입니다. 😁😁


트랜잭션 문제 해결 - 트랜잭션 AOP 이해

현재까지의 상태

  1. 트랜잭션이 없었다.
  • DB 데이터 정합성 문제가 발생한다.
  1. 트랜잭션을 도입했다. → set autocommit
  • 트랜잭션이 적용되어, 원자 단위로 일이 처리되었다.
  1. 각 DB 접근 기술마다 트랜잭션 사용 방법이 다르다 → TransactionManager
  • TransactionManager는 트랜잭션 접근 방법을 추상화했다. 이를 통해 Jdbc/JPA 기술에 대한 의존성을 제거했다.
  1. 서비스 계층에 Transaction 관련 반복 코드 다수 발생 → TransactionTemplate
  • TransactionTemplate은 트랜잭션의 시작/Commit/Rollback/자원회수 등을 처리해준다.

1~4번까지 점진적 코드의 개선이 있었다.

🎃 TranscationTemplate에는 한 가지 문제가 남는다. 서비스 계층에 transcationTemplate.execute()라는 코드가 남는다는 것이다. 이것은 서비스 계층이 트랜잭션 기술에 의존하는 것을 의미한다. 바꿔 이야기하면 서비스 계층에 비즈니스 로직 외에 부가 관심사가 들어간다는 것을 의미한다.

  • 이런 코드는 유지/보수가 어렵다. 왜냐하면 서비스 계층에서 트랜잭션을 사용하지 않겠다고 할 경우, 코드 전체가 수정되어야 하기 때문이다. 이 부분의 해결이 필요한 상태.

트랜잭션 AOP 이해

스프링 AOP를 통해 프록시를 도입하면 문제를 깔끔하게 해결할 수 있다.
🎈 @Transactional를 사용하면 스프링은 트랜잭션 AOP를 제공해준다. 트랜잭션 AOP는 AOP Proxy를 이용해서 트랜잭션과 관련된 부가 관심사를 비즈니스 로직과 분리시켜준다.

트랜잭션 AOP 프록시 도입 전

🎈 프록시를 도입하기 전에는 기존처럼 서비스의 로직에서 트랜잭션을 직접 시작한다.

트랜잭션 AOP 프록시 도입 후

🎈 트랜잭션 AOP를 이용한 프록시를 사용하면, 트랜잭션을 처리하는 객체와 비즈니스 로직 처리 객체를 명확하게 분리할 수 있다. 트랜잭션 AOP로 프록시를 생성하고, 프록시는 부가 관심사를 프록시 객체 내에서 처리해준다. 그리고 프록시는 부가 관심사 내에서 실제 대상이 되는 타겟 객체를 호출하면서 비즈니스 로직을 처리해준다. 즉, 핵심 관심사 / 부가 관심사에 따라 객체가 2개로 분리가 된다.

  • 프록시는 스프링이 다 만들어줌 !

트랜잭션 프록시 코드 예시

public class TransactionProxy {
 private MemberService target;
 public void logic() {
 //트랜잭션 시작
 TransactionStatus status = transactionManager.getTransaction(..);
 try {
 //실제 대상 호출
 target.logic();
 transactionManager.commit(status); //성공시 커밋
 } catch (Exception e) {
 transactionManager.rollback(status); //실패시 롤백
 throw new IllegalStateException(e);
 }
 }
}

트랜잭션 프록시 적용 후 서비스 코드 예시

public class Service {
 public void logic() {
 //트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
 bizLogic(fromId, toId, money);
 }
}

순수한 비즈니스 로직만 작성해줄 수 있게 됨 !

🎈 프록시 도입 후에는 트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다. 그리고 트랜잭션을 시작한 후에 실제 서비스를 대신 호출한다. 트랜잭션 프록시 덕분에 서비스 계층에는 순수한 비즈니스 로직만 남길 수 있다.

스프링이 제공하는 트랜잭션 AOP

스프링이 제공하는 AOP 기능을 사용하면 프록시를 매우 편리하게 적용할 수 있다. 스프링 AOP를 직접 사용해서 트랜잭션 처리해도 된다. 그렇지만 트랜잭션은 매우 중요하고, 일반적인 기능이기 때문에 스프링에서 제공해주는 트랜잭션 AOP를 사용하는 것이 좋다.

개발자는 트랜잭션 처리가 필요한 곳에 @Transactional 어노테이션만 붙여주면 된다. 스프링의 트랜잭션 AOP는 어노테이션 기반 AOP가 적용되서 트랜잭션 프록시 객체를 만들어준다.

@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
    bizLogic(fromId, toId, money);
}
  • 스프링이 제공하는 트랜잭션 AOP를 적용하기 위해 @Transactional 애노테이션을 추가했다.
  • 순수한 비즈니스 로직만 남기고, 트랜잭션 관련 코드는 모두 제거했다.
  • 스프링부트를 사용하면 트랜잭션 AOP를 처리하기 위해 필요한 스프링 빈들도 자동으로 등록해준다.

@Transactional 애노테이션을 이용해서 트랜잭션의 관심사 분리가 되었다. 이를 통해 유지/보수가 용이한 코드를 만들 수 있게 되었다.

참고

스프링 AOP를 적용하려면 어드바이저, 포인트컷, 어드바이스가 필요하다. 스프링은 트랜잭션 AOP를 처리를 위해 다음 클래스를 제공. 스프링부트를 사용하면 해당 빈들은 스프링 컨테이너에 자동으로 등록된다.

어드바이저 : BeanFactoryTransactionAttributeSourceAdvisor
포인트컷 : TransactionAttributeSourcePointcut
어드바이스 : TransactionAttributeSourcePointcut

트랜잭션 문제 해결 - 트랜잭션 AOP 적용

트랜잭션 AOP를 사용하는 새로운 서비스 클래스를 만들자

MemberServiceV3_3

package hello.jdbc.service;

import hello.jdbc.domain.Member;
import hello.jdbc.repository.MemberRepositoryV3;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.sql.SQLException;

/**
 * 트랜잭션 - @Transactional AOP
 */
@Slf4j
public class MemberServiceV3_3 {
    private final MemberRepositoryV3 memberRepository;

    public MemberServiceV3_3(MemberRepositoryV3 memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Transactional
    public void accountTransfer(String fromId, String toId, int money) throws SQLException {
        //비즈니스 로직
        bizLogic(fromId, toId, money);
    }
    private void bizLogic(String fromId, String toId, int money) throws
            SQLException {
        Member fromMember = memberRepository.findById(fromId);
        Member toMember = memberRepository.findById(toId);
        memberRepository.update(fromId, fromMember.getMoney() - money);
        validation(toMember);
        memberRepository.update(toId, toMember.getMoney() + money);
    }
    private void validation(Member toMember) {
        if (toMember.getMemberId().equals("ex")) {
            throw new IllegalStateException("이체중 예외 발생");
        }
    }
}

🎈 순수한 비즈니스 로직만 남기고, 트랜잭션 관련 코드는 모두 제거됨

  • memberRepository 인터페이스에만 의존하고 순수 서비스 로직만 남게됨.

🎈 스프링이 제공하는 트랜잭션 AOP를 적용하기 위해 @Transactional애노테이션을 추가했다.

🎈 @Transactional 애노테이션은 메서드에 붙여도 되고, 클래스에 붙여도 된다. 클래스에 붙이면 외부에서 호출 가능한 public메서드가 AOP 적용 대상이 된다.

MemberServiceV3_3Test - 테스트 코드

package hello.jdbc.service;
import hello.jdbc.domain.Member;
import hello.jdbc.repository.MemberRepositoryV3;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
import static hello.jdbc.connection.ConnectionConst.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
 * 트랜잭션 - @Transactional AOP
 */
@Slf4j
@SpringBootTest
class MemberServiceV3_3Test {
    public static final String MEMBER_A = "memberA";
    public static final String MEMBER_B = "memberB";
    public static final String MEMBER_EX = "ex";
    @Autowired
    MemberRepositoryV3 memberRepository;
    @Autowired
    MemberServiceV3_3 memberService;
    @AfterEach
    void after() throws SQLException {
        memberRepository.delete(MEMBER_A);
        memberRepository.delete(MEMBER_B);
        memberRepository.delete(MEMBER_EX);
    }
    @TestConfiguration
    static class TestConfig {
        @Bean
        DataSource dataSource() {
            return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
        }
        @Bean
        PlatformTransactionManager transactionManager() {
            return new DataSourceTransactionManager(dataSource());
        }
        @Bean
        MemberRepositoryV3 memberRepositoryV3() {
            return new MemberRepositoryV3(dataSource());
        }
        @Bean
        MemberServiceV3_3 memberServiceV3_3() {
            return new MemberServiceV3_3(memberRepositoryV3());
        }
    }
    @Test
    void AopCheck() {
        log.info("memberService class={}", memberService.getClass());
        log.info("memberRepository class={}", memberRepository.getClass());
        Assertions.assertThat(AopUtils.isAopProxy(memberService)).isTrue();
        Assertions.assertThat(AopUtils.isAopProxy(memberRepository)).isFalse();
    }
    @Test
    @DisplayName("정상 이체")
    void accountTransfer() throws SQLException {
        //given
        Member memberA = new Member(MEMBER_A, 10000);
        Member memberB = new Member(MEMBER_B, 10000);
        memberRepository.save(memberA);
        memberRepository.save(memberB);
        //when
        memberService.accountTransfer(memberA.getMemberId(),
                memberB.getMemberId(), 2000);
        //then
        Member findMemberA = memberRepository.findById(memberA.getMemberId());
        Member findMemberB = memberRepository.findById(memberB.getMemberId());
        assertThat(findMemberA.getMoney()).isEqualTo(8000);
        assertThat(findMemberB.getMoney()).isEqualTo(12000);
    }
    @Test
    @DisplayName("이체중 예외 발생")
    void accountTransferEx() throws SQLException {
        //given
        Member memberA = new Member(MEMBER_A, 10000);
        Member memberEx = new Member(MEMBER_EX, 10000);
        memberRepository.save(memberA);
        memberRepository.save(memberEx);
        //when
        assertThatThrownBy(() ->
                memberService.accountTransfer(memberA.getMemberId(), memberEx.getMemberId(),
                        2000))
                .isInstanceOf(IllegalStateException.class);
        //then
        Member findMemberA = memberRepository.findById(memberA.getMemberId());
        Member findMemberEx =
                memberRepository.findById(memberEx.getMemberId());
        //memberA의 돈이 롤백 되어야함
        assertThat(findMemberA.getMoney()).isEqualTo(10000);
        assertThat(findMemberEx.getMoney()).isEqualTo(10000);
    }
}

🎃 @SpringBootTest : 스프링 AOP를 적용하려면 스프링 컨테이너가 필요하다. 이 애노테이션이 있으면 테스트시 스프링 부트를 통해 스프링 컨테이너를 생성한다. 그리고 테스트에서 @Autowired 등을 통해 스프링 컨테이너가 관리하는 빈들을 사용할 수 있다. (스프링 컨테이너를 생성하고 스프링 빈 의존관계 주입도 해주는게 핵심 !)
근데 아직 스프링 빈으로 등록을 안해놨기 때문에 아직 의존관계 주입이 안돼 ! 그래서 이제 스프링 빈으로 등록을 해주자 ! → @TestConfiguration

🎃 @TestConfiguration : 테스트 안에서 내부 설정 클래스를 만들어서 사용하면서 이 에노테이션을 붙이면, 스프링 부트가 자동으로 만들어주는 빈들에 추가로 필요한 스프링 빈들을 등록하고 테스트를 수행할 수 있다.

🎃 TestConfig 메서드

  • DataSource 스프링에서 기본으로 사용할 데이터소스를 스프링 빈으로 등록한다. 추가로 트랜잭션 매니저에서도 사용한다.
  • DataSourceTransactionManager 트랜잭션 매니저를 스프링 빈으로 등록한다.
    • 스프링이 제공하는 트랜잭션 AOP는 스프링 빈에 등록된 트랜잭션 매니저를 찾아서 사용하기 때문에 트랜잭션 매니저를 스프링 빈으로 등록해두어야 한다.

트랜잭션 문제 해결 - 트랜잭션 AOP 정리

  1. 클라이언트가 비즈니스 로직을 호출한다.

  2. 비즈니스 로직은 프록시 객체를 호출한다.(@Transactional 애노테이션이 있기 때문에)

  3. 프록시 객체는 트랜잭션을 시작한다. 프록시 객체는 내부적으로 트랜잭션 매니저를 가지고 있다. 트랜잭션 매니저는 스프링 컨테이너를 통해 주입 받는다.

  4. 트랜잭션 매니저는 getTransaction()을 호출한다. 이 때, 트랜잭션 매니저는 데이터 소스로부터 커넥션을 가지고 온다. 가져온 커넥션에 setAutoCommit을 False로 설정해준다.

  5. 트랜잭션 매니저는 설정된 커넥션을 트랜잭션 동기화 매니저에게 저장해준다.

  6. 프록시 객체는 실제 비즈니스 로직을 호출한다. 비즈니스 로직은 Repository를 호출한다.

  7. Repository는 트랜잭션이 있어야 DB와 통신을 할 수 있는데, 이 때 Repository는 내부적으로 가지고 있는 DataSourceUtils.getConnection()을 이용해 트랜잭션 동기화 매니저에게서 동기화 된 커넥션을 받아온다.

  8. 다 끝나면 반환되어서, 트랜잭션 프록시 객체에서 Commit / RollBack을 처리해준다.

@Transactional 애노테이션 하나로 accountTransfer() 비즈니스 로직을 알아서 상황에 따라 트랜잭션 commit/rollback 해주네 !! 성공하거나.. 실패해서 이전 상태로 돌아가거나.. 테스트 성공.

선언적 트랜잭션 관리 vs 프로그래밍 방식 트랜잭션 관리

🎈 선언적 트랜잭션 관리

  • @Transactional 애노테이션 하나만 선언해서 트랜잭션을 처리하는 방법

🎈 프로그래밍 방식의 트랜잭션 관리

  • Transactionmanager/TransactionTemplate을 직접 작성해서 트랜잭션을 관리한다.

두 가지 방법에는 각각의 장점이 있다. @Transactional은 간편하게 이용할 수 있기 때문에 실무에서 많이 사용하는 방법이다. TranscationManager와 TransactionTemplate은 스프링 컨테이너가 없어도 사용할 수 있기 때문에 스프링 기술 의존성이 많이 줄어든다.

그냥 애노테이션 사용이 편해 !

정리

  • 스프링이 제공하는 @Transactional 덕분에 트랜잭션 관련 코드를 순수한 비즈니스 로직에서 제거할 수 있었다.

  • 개발자는 트랜잭션이 필요한 곳에 @Transactional 어노테이션을 하나 추가하기만 하면 된다.

  • @Transactional은 스프링 AOP 프록시 객체를 전달해준다. 이것은 Bean PostProcessor를 통해서 처리가 되기 때문에 스프링 컨테이너가 반드시 필요하다.

스프링 부트의 자동 리소스 등록

스프링 부트가 등장하기 이전에 개발자들은 DataSource와 TransactionManager를 개발자가 직접 스프링 빈으로 등록해서 사용했다. 그런데 스프링 부트로 개발을 시작한 개발자라면 DataSource나 TransactionManager를 직접 등록한 적이 없다.

DataSource + TransactionManager 직접 등록

@Bean
public DataSource dataSource() {
   HikariDataSource dataSource = new HikariDataSource();
   dataSource.setJdbcUrl(URL);
   dataSource.setUsername(USERNAME);
   dataSource.setPassword(PASSWORD);
   return dataSource;
}

@Bean
public TransactionManager transactionManager() {
   return new DataSourceTransactionManager(dataSource());
}
  • 기존에는 개발자가 DataSource/TransactionManager를 직접 스프링 빈으로 등록해야 했다.
  • 스프링부트는 DataSource/TransactionManager를 스프링 빈으로 등록해준다 !(편하다 !)

DataSource : 스프링부트가 자동등록

  • 스프링 부트는 DataSource를 스프링 빈으로 자동 등록해준다.
  • 자동 등록되는 스프링 빈의 이름은 dataSource다.
  • 개발자가 직접 DataSource를 빈으로 등록하면, 스프링 부트는 DataSource를 자동으로 등록하지 않는다.
  • 스프링 부트는 application.properties에 있는 설정을 참고해서 DataSource를 등록한다.
    • 스프링 부트는 기본적으로 HikariCP를 DataSource로 등록해준다.
    • Connection Pool 설정을 application.properties에서 처리할 수 있다.
    • DataSourceURL이 지정되지 않는 경우, 내장 DB(메모리 DB)를 생성하려고 시도한다.

🎈 application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

🧡 TransactionManager : 스프링 부트가 자동 등록

  • 스프링 부트는 적절한 트랜잭션 매니저(PlatformTransactionManager)를 자동으로 스프링 빈에 등록해준다.

  • 자동으로 등록되는 스프링 빈의 이름은 'transactionManager'다.

  • 개발자가 직접 트랜잭션 매니저를 빈으로 등록하면 스프링 부트는 트랜잭션 매니저를 자동으로 등록하지 않는다.

TransactionManager는 DB 접근 기술을 어떤 것을 사용하느냐에 따라 다른 TransactionManager 구현체가 필요하다. 그렇다면 스프링부트는 어떤 것을 기준으로 TransactionManager를 생성해서 등록해줄까? 스프링부트는 현재 등록된 라이브러리를 참고해서 적절한 TransactionManager를 등록해준다.

Jdbc 라이브러리가 존재할 경우 DataSourceTransactionManager를 등록해준다. JPA를 사용하면 JpaTransactionManager를 등록해준다. 둘다 사용하는 경우에는 JpaTransactionManager를 등록해주는데, JpaTransactionManager는 DataSourceTransactionManager의 대부분의 기술을 대응해줄 수 있기 때문에 등록해준다.

실습 - 데이터소스, 트랜잭션 매니저 직접 등록

@TestConfiguration
static class TestConfig {
 @Bean
 DataSource dataSource() {
 return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
 }
 @Bean
 PlatformTransactionManager transactionManager() {
 return new DataSourceTransactionManager(dataSource());
 }
 @Bean
 MemberRepositoryV3 memberRepositoryV3() {
 return new MemberRepositoryV3(dataSource());
 }
 @Bean
 MemberServiceV3_3 memberServiceV3_3() {
 return new MemberServiceV3_3(memberRepositoryV3());
 }
}

🎈 이전에 작성한 코드. 이렇게 데이터소스와 트랜잭션 매니저를 직접 등록하면 스프링 부트는 데이터소스와 트랜잭션 매니저를 자동으로 등록하지 않는다.

🎈 이번에는 스프링부트가 제공하는 자동 등록을 이용해서 데이터소스와 트랜잭션 매니저를 편리하게 적용해보자.
→ 먼저 application.properties에 등록해야지... ↓

실습 - 데이터소스와 트랜잭션 매니저 자동 등록

application.properties

  • dataSource 자동 등록
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

🎃 MemberServiceV3_4Test

package hello.jdbc.service;
import hello.jdbc.domain.Member;
import hello.jdbc.repository.MemberRepositoryV3;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;
import java.sql.SQLException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
 * 트랜잭션 - @Transactional AOP
 */
@Slf4j
@SpringBootTest
class MemberServiceV3_4Test {
    public static final String MEMBER_A = "memberA";
    public static final String MEMBER_B = "memberB";
    public static final String MEMBER_EX = "ex";
    @Autowired
    MemberRepositoryV3 memberRepository;
    @Autowired
    MemberServiceV3_3 memberService;
    @AfterEach
    void after() throws SQLException {
        memberRepository.delete(MEMBER_A);
        memberRepository.delete(MEMBER_B);
        memberRepository.delete(MEMBER_EX);
    }
    @TestConfiguration
    static class TestConfig {
        private final DataSource dataSource;

        TestConfig(DataSource dataSource) {  //스프링 컨테이너에 등록된 dataSource를 의존관계 주입해줌.
            this.dataSource = dataSource;
        }

        @Bean
        MemberRepositoryV3 memberRepositoryV3() {
            return new MemberRepositoryV3(dataSource);
        }
        @Bean
        MemberServiceV3_3 memberServiceV3_3() {
            return new MemberServiceV3_3(memberRepositoryV3());
        }
    }
    @Test
    void AopCheck() {
        log.info("memberService class={}", memberService.getClass());
        log.info("memberRepository class={}", memberRepository.getClass());
        Assertions.assertThat(AopUtils.isAopProxy(memberService)).isTrue();
        Assertions.assertThat(AopUtils.isAopProxy(memberRepository)).isFalse();
    }
    @Test
    @DisplayName("정상 이체")
    void accountTransfer() throws SQLException {
        //given
        Member memberA = new Member(MEMBER_A, 10000);
        Member memberB = new Member(MEMBER_B, 10000);
        memberRepository.save(memberA);
        memberRepository.save(memberB);
        //when
        memberService.accountTransfer(memberA.getMemberId(),
                memberB.getMemberId(), 2000);
        //then
        Member findMemberA = memberRepository.findById(memberA.getMemberId());
        Member findMemberB = memberRepository.findById(memberB.getMemberId());
        assertThat(findMemberA.getMoney()).isEqualTo(8000);
        assertThat(findMemberB.getMoney()).isEqualTo(12000);
    }
    @Test
    @DisplayName("이체중 예외 발생")
    void accountTransferEx() throws SQLException {
        //given
        Member memberA = new Member(MEMBER_A, 10000);
        Member memberEx = new Member(MEMBER_EX, 10000);
        memberRepository.save(memberA);
        memberRepository.save(memberEx);
        //when
        assertThatThrownBy(() ->
                memberService.accountTransfer(memberA.getMemberId(), memberEx.getMemberId(),
                        2000))
                .isInstanceOf(IllegalStateException.class);
        //then
        Member findMemberA = memberRepository.findById(memberA.getMemberId());
        Member findMemberEx =
                memberRepository.findById(memberEx.getMemberId());
        //memberA의 돈이 롤백 되어야함
        assertThat(findMemberA.getMoney()).isEqualTo(10000);
        assertThat(findMemberEx.getMoney()).isEqualTo(10000);
    }
}
  • 기존( MemberServiceV3_3Test )과 같은 코드이고 TestConfig 부분만 다르다. (dataSource, transactionManager가 빠짐)

  • 데이터소스와 트랜잭션 매니저를 스프링 빈으로 등록하는 코드가 생략되었다. 따라서 스프링 부트가 application.properties 에 지정된 속성을 참고해서 데이터소스와 트랜잭션 매니저를 자동으로 생성해준다.

  • 코드에서 보는 것처럼 생성자를 통해서 스프링부트가 만들어준 데이터소스 빈을 주입 받을 수도 있다. 실행해보면 모든 테스트가 정상 수행되는 것을 확인할 수 있다.

정리

스프링부트가 제공하는 DataSource/TransactionManager의 자동 빈 등록 기능을 사용하자. → 편리해 !

  • DataSource는 application.properties를 이용해 편리하게 설정을 할 수 있다.
  • transactionManager는 스프링에 자동으로 등록되어 있기에 그냥 쓰면 돼 !
profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글