본문 바로가기
스프링/스프링 DB 접근

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

by drCode 2025. 4. 11.
728x90
반응형

※ 스프링 부트 등장 전

개발자가 DataSource와 TxManager를 개발자가 직접 스프링 빈에 등록해서 사용해야했다.


※ 스프링 부트 등장 후

스프링부트에서 자동으로 등록해주는 방법이 생겼다.

 

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

@Bean
DataSource dataSource() {
	return new DriverManagerDataSource(URL, USERNAME, PASSWORD);
}

@Bean
PlatformTransactionManager transactionManager() {
	return new DataSourceTransactionManager(dataSource());
}

 

기존에는 이렇게 데이터소스와 트랜잭션 매니저를 직접 스프링 빈으로 등록해야 했다.

그런데 스프링 부트가 나오면서 많은 부분이 자동화되었다.

 

이전에는 주로 XML로 등록하고 관리했다

 

★ 예전 Spring 3 이하 버전에서는 XML 기반으로 설정을 많이 했다.

context-dataSource.xml 같은 파일에

데이터소스(DataSource)트랜잭션 매니저(Transaction Manager) 를 다음과 같이 등록하곤 했다.

 

📁 context-dataSource.xml 예시

 

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd
         http://www.springframework.org/schema/tx
         http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 1. 데이터소스 설정 (예: Apache DBCP 사용 시) -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/your_database"/>
        <property name="username" value="your_username"/>
        <property name="password" value="your_password"/>
        <property name="initialSize" value="5"/>
        <property name="maxActive" value="10"/>
    </bean>

    <!-- 2. 트랜잭션 매니저 설정 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 3. 어노테이션 기반 트랜잭션 활성화 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

 

🔍 부가 설명

  • BasicDataSource: 예전에는 Apache Commons DBCP를 많이 사용했다.
    HikariCP는 Spring Boot 시대로 넘어가면서 많이 쓰이게 됐다.
  • <tx:annotation-driven>: @Transactional 어노테이션을 사용하려면 반드시 활성화해줘야 한다.
  • 이 설정은 JdbcTemplate, MyBatis, Hibernate 등과 함께 사용된다.

 

데이터소스 - 자동 등록

스프링 부트는 데이터소스(DataSource)를 스프링 빈에 자동으로 등록한다.

자동으로 등록되는 스프링 빈 이름 : dataSource

참고로 개발자가 직접 데이터소스를 빈으로 등록하면 스프링 부트는 데이터소스를 자동으로 등록하지 않는다

 

스프링 부트는 다음과 같이 application.properties 에 있는 속성을 사용해서 DataSource 를 생성한다.

그리고 스프링 빈에 등록한다

 

application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/사용하는DB명
spring.datasource.username=sa
spring.datasource.password=

 

스프링 부트가 기본으로 생성하는 데이터소스는 커넥션풀을 제공하는 HikariDataSource 이다.

커넥션풀과 관련된 설정도 application.properties 를 통해서 지정할 수 있다.

spring.datasource.url 속성이 없으면 내장 데이터베이스(메모리 DB)를 생성하려고 시도한다

 

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

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

자동으로 등록되는 스프링 빈 이름: transactionManager

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

 

어떤 트랜잭션 매니저를 선택할지는 현재 등록된 라이브러리를 보고 판단하는데,

JDBC를 기술을 사용하면 DataSourceTransactionManager 를 빈으로 등록하고,

JPA를 사용하면 JpaTransactionManager 를 빈으로 등록한다.

둘다 사용하는 경우 JpaTransactionManager 를 등록한다.

참고로 JpaTransactionManagerDataSourceTransactionManager 가 제공하는 기능도 대부분 지원한다.

 

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

@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

spring.datasource.url=jdbc:h2:tcp://localhost/~/dbAccess
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.Data;
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;

/**
 *  트랜잭션 - DataSource, transactionManager 자동 등록
 * */
@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
    private MemberRepositoryV3 memberRepository;
    @Autowired
    private MemberServiceV3_3 memberService;

    @TestConfiguration
    static class TestContextConfig {

        private final DataSource dataSource;

        public TestContextConfig(DataSource dataSource) {
            this.dataSource = dataSource;
        }

        @Bean
        MemberRepositoryV3 memberRepositoryV3() {
            return new MemberRepositoryV3(dataSource);
        }

        @Bean
        MemberServiceV3_3 memberServiceV3() {
            return new MemberServiceV3_3(memberRepositoryV3());
        }
    }

    @AfterEach
    void after() throws SQLException {
        memberRepository.delete(Member_A);
        memberRepository.delete(Member_B);
        memberRepository.delete(Member_EX);
    }

    @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("memberA", 10000);
        Member memberB = new Member("memberB", 10000);
        memberRepository.save(memberA);
        memberRepository.save(memberB);

        // when
        log.info("TX START");
        memberService.accountTransfer(memberA.getMemberId(), memberB.getMemberId(), 2000);
        log.info("TX END");

        // 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("memberA", 10000);
        Member memberEx = new 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 부분만 다르다.

데이터소스와 트랜잭션 매니저를 스프링 빈으로 등록하는 코드가 생략되었다.

따라서 스프링 부트가 application.properties 에 지정된 속성을 참고해서 데이터소스와 트랜잭션 매니저를 자동으로 생성해준다

코드에서 보는 것처럼 생성자를 통해서 스프링 부트가 만들어준 데이터소스 빈을 주입 받을 수도 있다.

 

실행해보면 모든 테스트가 정상 수행되는 것을 확인할 수 있다.

 

정리하면,

데이터소스와 트랜잭션 매니저는 스프링 부트가 제공하는 자동 빈 등록 기능을 사용하는 것이 편리하다.

추가로 application.properties 를 통해 설정도 편리하게 할 수 있다

 

스프링 부트의 데이터소스 자동 등록에 대한 스프링부트 공식 메뉴얼

https://docs.spring.io/spring-boot/redirect.html?page=data#data.sql.datasource.production

 

https://docs.spring.io/spring-boot/redirect.html?page=data#data.sql.datasource.production

 

docs.spring.io

자세한 설정 속성은 다음을 참고하자

Common Application Properties :: Spring Boot

 

Common Application Properties :: Spring Boot

 

docs.spring.io

 

728x90
반응형

댓글