Inflearn brand logo image

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

Dongni님의 프로필 이미지
Dongni

작성한 질문수

스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술

순수 JDBC

@Transactional을 붙여도 Rollback이 되지 않는 문제는 해결했으나 이유륾 모름

작성

·

97

·

수정됨

0

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.

1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)

[질문 내용]

해결했습니다.

복붙하는 과정에서

private Connection getConnection() {
    return DataSourceUtils.getConnection(dataSource);
}

이 부분이 있는 걸 몰랐네요

 

저는 conn = dataSource.getConnection() 으로 진행해서 문제가 생긴거였습니다

 

그런데 추가적인 질문으로 이렇게 했을 때는 롤백이 되지 않는 이유가 무엇인가요?

 

 


강의 코드와 똑같이 수정했음에도 롤백되지 않고 계속 DB에 반영이 됩니다.

 

참고로 다른 질문글의 conn 관련된 수정도 이미 했음에도 롤백되지 않습니다.

 

JdbcMemberRepository

package hello.hello_spring.Repository;

import hello.hello_spring.Domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {

    // DB와 연동하려면 Datasource가 필요함
    private final DataSource dataSource;

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

    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";

        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

            pstmt.setString(1, member.getName());

            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();

            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional<Member> findById(Long id) {
        String sql = "select * from member where id = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);

            rs = pstmt.executeQuery();

            if (rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }

        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }


    @Override
    public List<Member> findAll() {
        String sql = "select * from member";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement(sql);

            rs = pstmt.executeQuery();

            List<Member> members = new ArrayList<>();
            while (rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                members.add(member);
            }

            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

    @Override
    public Optional<Member> findByName(String name) {
        String sql = "select * from member where name = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = dataSource.getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();

            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
    }
    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}

 

SpringConfig

package hello.hello_spring;

import hello.hello_spring.Repository.JdbcMemberRepository;
import hello.hello_spring.Repository.MemberRepository;
import hello.hello_spring.Repository.MemoryMemberRepository;
import hello.hello_spring.Service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private DataSource dataSource;

    @Autowired
    public SpringConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new JdbcMemberRepository(dataSource);
    }
}

 

 

MemberServiceIntegrationTest

package hello.hello_spring.Service;

import hello.hello_spring.Domain.Member;
import hello.hello_spring.Repository.MemberRepository;
import hello.hello_spring.Repository.MemoryMemberRepository;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

// 테스트는 반복해서 시도할 수 있어야 하기 때문에 DB에 데이터를 인서트 쿼리하고 '롤백' 해주는 것 (정확히는 DB에 반영을 안하는 것)
// 이를 위해서 @Transactional 사용
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {

    // 테스트는 특수한 용도이기 때문에 일회성(?)으로 필드 주입을 해도 무관
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    void 회원가입() {
        // given
        Member member = new Member();
        member.setName("spring");

        // when
        Long saveId = memberService.join(member);

        // then
        Member one = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(one.getName());
    }

    @Test
    public void 중복_회원_예외() {
        // given
        Member member1 = new Member();
        member1.setName("spring");

        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);
        IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> memberService.join(member2));

        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");

//        try {
//            memberService.join(member2);
//        } catch (IllegalArgumentException e) {
//            assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
//        }
        // then

    }
}

 

답변 1

0

안녕하세요, 유동훈님. 인프런 AI 인턴입니다.

@Transactional 어노테이션을 사용했지만 롤백되지 않는 문제에 대해 질문하셨네요. 이 문제는 주로 트랜잭션 설정이나 JDBC의 연결 설정에서 비롯될 수 있습니다. 아래에서 몇 가지 해결 방법을 제안드립니다.

체크리스트 및 해결 방법:

  1. 트랜잭션 관리 확인:

    • @Transactional이 의도한 방식대로 작동하게 하려면 스프링의 트랜잭션 관리가 제대로 설정되어 있어야 합니다.
    • @SpringBootTest를 사용하여 트랜잭션이 자동으로 관리되게 설정되어 있는지 확인하세요.
    • 데이터베이스 설정에 트랜잭션 관리가 누락되었는지 확인하세요. 특히, 데이터베이스별로 트랜잭션 동작이 다를 수 있으므로 JDBC 관련 로그를 확인하여 트랜잭션 경계가 제대로 설정되는지 확인이 필요합니다.
  2. DataSourceUtils 사용:

    • JdbcMemberRepository에서 DataSourceUtils.getConnection()DataSourceUtils.releaseConnection()을 사용하여 커넥션을 관리하고 있는지 체크하세요. 이 메소드를 사용해야 트랜잭션 경계 내에서 커넥션이 관리됩니다.
  3. JDBC 설정과 데이터베이스 설정 (Conn 관련):

    • 데이터베이스 커넥션 풀 사용 여부 및 설정을 확인하세요. 일부 커넥션 풀 설정이 있을 경우 트랜잭션 경계가 제대로 처리되지 않을 수 있습니다.
    • 기본 설정을 변경한 경우, 특히 테스트 환경에서 @Transactional이 정상적으로 작동하도록 관련 설정이 반영되었는지 확인할 필요가 있습니다.
  4. Transactional 설정 확인:

    • 모든 @Test 메서드가 @Transactional에 의해 관리되는지 확인하세요.
    • 트랜잭션이 자동으로 커밋되거나 롤백되지 않도록 설정된 부분이 있는지 확인합니다.

관련 참고 링크

롤백되지 않는 문제를 경험한 다른 사용자들의 사례를 확인하시어, 문제를 해결하는 데 참고할 수 있습니다.

위의 설명이 해결에 도움이 되길 바라며, 만약 추가적인 도움이 필요하거나 질문이 있으시다면 주저하지 말고 문의해 주세요. 곧 지식 공유자가 더 상세하게 답변해 드릴 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

Dongni님의 프로필 이미지
Dongni

작성한 질문수

질문하기