※ 스프링 부트 등장 전
개발자가 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 를 등록한다.
참고로 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
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
'스프링 > 스프링 DB 접근' 카테고리의 다른 글
[Spring] 트랜잭션 AOP (0) | 2025.04.08 |
---|---|
[Spring] 트랜잭션 템플릿 (p.s 템플릿 콜백 패턴) (0) | 2025.04.08 |
[Spring] 트랜잭션 매니저 (0) | 2025.04.01 |
[Spring] 트랜잭션 추상화와 동기화 (스프링으로 트랜잭션 문제점 해결) (1) | 2025.01.03 |
[Spring] 트랜잭션 개념 적용 예제 (0) | 2024.12.23 |
댓글