[Spring DB] 자동 리소스 등록

DaeHoon·2022년 7월 23일
0

4-10. 스프링 부트의 자동 리소스 등록

  • 스프링 부트가 등장하기 이전에는 데이터소스와 트랜잭션 매니저를 개발자가 직접 스프링 빈으로 등록해서 사용했다.
  • 그런데 스프링 부트로 개발을 시작한 개발자라면 데이터소스나 트랜잭션 매니저를 직접 등록한 적이 없을 것이다.

데이터소스와 트랜잭션 매니저를 스프링 빈으로 직접 등록

  @Bean
  DataSource dataSource(){
       return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
  }
  
  @Bean
  PlatformTransactionManager transactionManager() {
      return new DataSourceTransactionManager(dataSource());
  }
  • 기존에는 이렇게 데이터소스와 트랜잭션 매니저를 직접 스프링 빈으로 등록해야 했다. 그런데 스프링 부트가 나오면서 많은 부분이 자동화되었다. (더 오래전에 스프링을 다루어왔다면 해당 부분을 주로 XML로 등록하고 관리했을 것이다.)

데이터소스 - 자동 등록

  • 스프링 부트는 데이터 소스를 스프링 빈에 자동으로 등록한다
  • 자동으로 등록되는 스프링 빈 이름: dataSource
  • 참고로 개발자가 직접 데이터소스를 빈으로 등록하면 스프링 부트는 데이터소스를 자동으로 등록하지 않는다.

이때 스프링 부트는 다음과 같이 application.properties 에 있는 속성을 사용해서 DataSource 를 생성한다. 그리고 스프링 빈에 등록한다.\

application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=
  • 스프링 부트가 기본으로 생성하는 데이터소스는 커넥션풀을 제공하는 HikariDataSource 이다. 커넥션풀과 관련된 설정도 application.properties 를 통해서 지정할 수 있다.
  • spring.datasource.url 속성이 없으면 내장 데이터베이스(메모리 DB)를 생성하려고 시도한다.

트랜잭션 매니저 - 자동 등록

  • 스프링 부트는 적절한 트랜잭션 매니저(PlatformTransactionManager)를 자동으로 스프링 빈에 등록한다.
  • 자동으로 등록되는 스프링 빈 이름: transactionManager
  • 참고로 개발자가 직접 트랜잭션 매니저를 빈으로 등록하면 스프링 부트는 트랜잭션 매니저를 자동으로 등록하지 않는다.

어떤 트랜잭션 매니저를 선택할지는 현재 등록된 라이브러리를 보고 판단하는데, JDBC를 기술을 사용하면 DataSourceTransactionManager 를 빈으로 등록하고, JPA를 사용하면 JpaTransactionManager 를 빈으로 등록한다. 둘다 사용하는 경우 JpaTransactionManager를 등록한다. 참고로 JpaTransactionManager는 DataSourceTransactionManager가 제공하는 기능도 대부분 지원한다.

MemberServiceV3_4Test

package hello.jbdc.service

import hello.jbdc.Log
import hello.jbdc.connection.ConnectionConst
import hello.jbdc.domain.Member
import hello.jbdc.repository.MemberRepositoryV3
import org.assertj.core.api.Assertions
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
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 java.sql.SQLException
import javax.sql.DataSource


@SpringBootTest
internal class MemberServiceV3_4Test: Log{

  val MEMBER_A = "memberA"
  val MEMBER_B = "memberB"
  val MEMBER_EX = "ex"

  @Autowired
  lateinit var memberRepository: MemberRepositoryV3

  @Autowired
  lateinit var memberService: MemberServiceV3_3


  @AfterEach
  @Throws(SQLException::class)
  fun after(){
    memberRepository.delete(MEMBER_A);
    memberRepository.delete(MEMBER_B);
    memberRepository.delete(MEMBER_EX);
  }

  @TestConfiguration
   class TestConfig(
    private val dataSource: DataSource
   ){
   
    @Bean
    fun memberRepositoryV3() = MemberRepositoryV3(dataSource)

    @Bean
    fun memberServiceV3_3() = MemberServiceV3_3(memberRepositoryV3())
  }

  @Test
  fun AopCheck() {
    logger.info("memberService class={}", memberService.javaClass)
    logger.info("memberRepository class={}", memberRepository.javaClass)
    Assertions.assertThat(AopUtils.isAopProxy(memberService)).isTrue()
    Assertions.assertThat(AopUtils.isAopProxy(memberRepository)).isFalse()
  }

  @Test
  @DisplayName("정상 이체")
  @Throws(SQLException::class)
  fun accountTransfer() {
    //given
    val memberA = Member(MEMBER_A, 10000)
    val memberB = Member(MEMBER_B, 10000)
    memberRepository.save(memberA)
    memberRepository.save(memberB)
    //when
    memberService.accountTransfer(memberA.memberId,
      memberB.memberId, 2000)
//    //then
    val findMemberA: Member = memberRepository.findById(memberA.memberId)
    val findMemberB: Member = memberRepository.findById(memberB.memberId)
    assertThat(findMemberA.money).isEqualTo(8000)
    assertThat(findMemberB.money).isEqualTo(12000)
  }

  @Test
  @DisplayName("이체 중 예외 발생")
  fun accountTransferEx(){
    //given
    val memberA = Member(MEMBER_A, 10000)
    val memberEx = Member(MEMBER_EX, 10000)
    memberRepository.save(memberA)
    memberRepository.save(memberEx)
    // when

    assertThatThrownBy {
      memberService.accountTransfer(memberA.memberId, memberEx.memberId,
        2000)
    }
      .isInstanceOf(IllegalStateException::class.java)

    // then
    val findMemberA = memberRepository.findById(memberA.memberId)
    val findMemberEx = memberRepository.findById(memberEx.memberId)

    assertThat(findMemberA.money).isEqualTo(10000)
    assertThat(findMemberEx.money).isEqualTo(10000)

  }
}
  • 데이터 소스와 트랜잭션 매니저를 스프링 빈으로 등록하는 코드가 생략되었다. application.properties 에 지정된 속성을 참고해서 데이터소스와 트랜잭션 매니저를 자동으로 생성해줬기 때문이다.
  • 코드에서 보는 것 처럼 생성자를 통해서 스프링 부트가 만들어준 데이터소스 빈을 주입 받을 수도 있다.

정리

  • 데이터소스와 트랜잭션 매니저는 스프링 부트가 제공하는 자동 빈 등록 기능을 사용하는 것이 편리하다.
  • 추가로 application.properties 를 통해 설정도 편리하게 할 수 있다.
profile
평범한 백엔드 개발자

0개의 댓글