인프런 커뮤니티 질문&답변

한재덕님의 프로필 이미지
한재덕

작성한 질문수

토비의 스프링 부트 - 이해와 원리

JdbcTemplate과 트랜잭션 매니저 구성

DataSourceConfig에서 @EnableTransactionManagement를 사용하면 DataSourceTest가 안 되는것에 대한 질문이 있습니다.

해결된 질문

작성

·

1K

·

수정됨

0

 안녕하세요 토비님. 강의 정말 잘 듣고있습니다!

 

JdbcTemplate과 트랜잭션 매니저 구성 강의를 듣다가 @EnableTransactionManagement으로 트랜잭션(tx) 관리 기능을 열어주고 기존 DataSourceTest.java 예제를 실행하니 java.lang.IllegalStateException: Failed to unwrap proxied object 에러가 계속 발생합니다.

DataSourceConfig.java에서 @EnableTransactionManagement 애노테이션을 제외를 하면 DataSourceTest.java 테스트가 정상적으로 잘 돌아가더라고요...음

 

java와 spring 버전, 라이브러리는 모두 동일하게 설정을 했습니다. 제 소스 코드는 하단에 있습니다!

 

버전 문제로 해당 예제가 안 돌아가는 것인지? 왜 안 되는지 궁금합니다.. 바쁘실텐데 이유나 원인을 아시면 알려주시면 감사하겠습니다.

 

에러코드

java.lang.IllegalStateException: Failed to unwrap proxied object

	at org.springframework.test.util.AopTestUtils.getUltimateTargetObject(AopTestUtils.java:105)
	at org.springframework.boot.test.mock.mockito.SpringBootMockResolver.resolve(SpringBootMockResolver.java:35)
	at org.mockito.internal.util.MockUtil.resolve(MockUtil.java:118)
	at org.mockito.internal.util.MockUtil.isMock(MockUtil.java:108)
	at org.mockito.internal.util.DefaultMockingDetails.isMock(DefaultMockingDetails.java:32)
	at org.springframework.boot.test.mock.mockito.MockReset.get(MockReset.java:106)
	at org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener.resetMocks(ResetMocksTestExecutionListener.java:82)
	at org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener.resetMocks(ResetMocksTestExecutionListener.java:70)
	at org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener.beforeTestMethod(ResetMocksTestExecutionListener.java:57)
	at org.springframework.test.context.TestContextManager.beforeTestMethod(TestContextManager.java:293)

 

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.7.6'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'tobyspring'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '11'
}

repositories {
	mavenCentral()
	maven {
		url 'https://repo.clojars.org'
		name 'Clojars'
	}
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework:spring-jdbc'
	implementation 'hikari-cp:hikari-cp:3.0.1'
	runtimeOnly('com.h2database:h2:2.1.214')
	// spring-boot-starter-undertow
//	implementation 'org.springframework.boot:spring-boot-starter-jetty'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

tasks.named('test') {
	useJUnitPlatform()
}

 

DataSourceTest


package tobyspring.helloboot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

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

@HellobootTest
public class DataSourceTest {

    @Autowired
    DataSource dataSource;

    @Test
    public void connect() throws SQLException {
        Connection connection = dataSource.getConnection();
        connection.close();
    }
}

 

HelloBootTest

package tobyspring.helloboot;

import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.Transactional;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = HellobootApplication.class)
@TestPropertySource("classpath:/application.properties")
@Transactional
public @interface HellobootTest {
}

 

DataSourceConfig.java

package tobyspring.config.autoconfig.datasource;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tobyspring.config.ConditionalMyOnClass;
import tobyspring.config.MyAutoConfiguration;
import tobyspring.config.autoconfig.EnableMyConfigurationProperties;

import javax.sql.DataSource;
import java.sql.Driver;

@MyAutoConfiguration
@ConditionalMyOnClass("org.springframework.jdbc.core.JdbcOperations")
@EnableMyConfigurationProperties(MyDataSourceProperties.class)
@EnableTransactionManagement
public class DataSourceConfig {

    @Bean
    @ConditionalMyOnClass("com.zaxxer.hikari.HikariDataSource")
    @ConditionalOnMissingBean
    DataSource hikariDataSource(MyDataSourceProperties properties) {
        HikariDataSource dataSource = new HikariDataSource();

        dataSource.setDriverClassName(properties.getDriverClassName());
        dataSource.setJdbcUrl(properties.getUrl());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());

        return dataSource;
    }

    @Bean
    @ConditionalOnMissingBean
    DataSource dataSource(MyDataSourceProperties properties) throws ClassNotFoundException {
        SimpleDriverDataSource dataSource = new SimpleDriverDataSource();

        dataSource.setDriverClass((Class<? extends Driver>) Class.forName(properties.getDriverClassName()));
        dataSource.setUrl(properties.getUrl());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());

        return dataSource;
    }

    @Bean
    @ConditionalOnSingleCandidate(DataSource.class)
    @ConditionalOnMissingBean
    JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    @ConditionalOnSingleCandidate(DataSource.class)
    @ConditionalOnMissingBean
    JdbcTransactionManager jdbcTransactionManager(DataSource dataSource) {
        return new JdbcTransactionManager(dataSource);
    }
}

답변 2

0

한재덕님의 프로필 이미지
한재덕
질문자

아하... 해당 강의 예제에서는 JdbcTransactionManager 설명하시는 4분 20초 정도에는 작성이 되어 있어서 같이 사용해야 하는줄 알았습니다!

 

@EnableTransactionManagement에 대해서 공부해보겠습니다.

 

감사합니다!!

토비님의 프로필 이미지
토비
지식공유자

@EnableTransactionManagement는 애노테이션을 이용하는 방식의 트랜잭션 관리가 가능하도록 설정할 때만 필요합니다.
예제에서 처럼 JdbcTemplate으로 사용할 때는 굳이 필요하지 않은데요.

위에 올려주신 코드를 다시 보니 JdbcTransactionManager까지 빈으로 등록이 되어있네요.

그래서 제가 해당 강의 부분의 예제를 가져와서 @EnableTransactionManagement를 추가해봤는데 테스트를 실행하면 아무 문제없이 잘 동작합니다. 에러가 났던 건 다른 이유 때문인 듯한데요.

강의 예제 흐름상 @EnableTransactionManagement이 필요한 것은 아니지만, 그래도 정말 궁금하시다면 에러가 나는 상태의 프로젝트를 github에 올려서 공유해주시면 제가 받아서 한번 확인해드리겠습니다.

한재덕님의 프로필 이미지
한재덕
질문자

넵 안녕하세요 토비님. 저도 다시 확인해보니 잘 됩니다!

 

아마 제가 Java 11 -> 17로 버전을 몇 번씩 바꾸고 테스트도 해봐서 gradle-wrapper.properties에서 graadle 설정값이 달라서 의존성들이 꼬였던 것 같습니다!!

 

바쁘신 와중에도 감사합니다!!

0

토비님의 프로필 이미지
토비
지식공유자

@EnableTransactionManagement는 왜 넣으셨죠. 😃

@EnableTransactionManagement이 동작하려면 PlatformTransactionManager 타입의 실제 트랜잭션을 생성하고 관리하는 빈이 등록되어 있어야 합니다. 예제에서 만든 간단한 DataSource 자동구성에는 트랜잭션 매니저를 넣지 않았습니다. 그래서 에러가 나는 것이죠. 정식으로 스프링이 만든 DataSourceTransactionManagerAutoConfiguration에서는 이 트랜잭션 매니저 빈이 자동구성으로 등록됩니다. 강의 예제에서는 간단한 설명을 위해서 트랜잭션 관련 빈을 넣지 않았기 때문에 @EnableTransactionManagement은 사용할 수 없습니다.

@EnableTransactionManagement 애노테이션 자바 문서를 참고해보세요.

한재덕님의 프로필 이미지
한재덕

작성한 질문수

질문하기