개발자로 후회없는 삶 살기

[문법] JDBC 추상화를 한 이유 본문

[백엔드]/[spring | 학습기록]

[문법] JDBC 추상화를 한 이유

몽이장쥰 2023. 5. 1. 14:09

서론

※ 과거에 기록한 내용에서 중요한 부분만 발췌하여 모두가 이해하기 쉽게 다시 서술한다.

 

본론

- 프로젝트 생성

프로젝트를 생성합니다. web으로 db를 다루기 전에 db에 접근하는 것부터 알아볼 것이기 때문에 위 3개의 라이브러리만 준비합니다.

 

- h2 설치

h2는 테스트할 때 간단하게 사용하기 좋은 db입니다. 최신 버전을 다운 받으면 안되고 스프링이 이전 버전을 지원하여 1.4.200 버전을 다운 받습니다.

> 실행하고 최초의 한 번은 이렇게 맞추고 연결 실험을 해야합니다. 이렇게 하면 test.mv.db가 생성되고 그 이후부터는 다르게 접근해야 합니다. 저는 이전에 한 번 해서 최초 접근은 생략합니다.

 

-> 테이블 생성

테이블을 하나 만듭니다. id와 회원의 돈이 들어가 있습니다. 이제 이후에 어플에서 이 db의 테이블에 접근해서 데이터를 넣고 빼고 해볼 것입니다.

 

-> jdbc 등장 이유

어플을 개발할 때 중요한 데이터는 대부분 db에 보관합니다.

그림을 보면 보통 이런 구조로 되어있는데 앱이나 웹에서 어플 서버에 요청을 보내면 서버에서 필요한 로직을 수행하고 필요한 데이터를 db에 sql을 전달해서 db에 보관하게 됩니다. 클라가 서버를 통해 데이터를 저장하거나 조회하면 서버는 다음 과정을 통해서 데이터베이스를 사용합니다.

 

1. 커넥션 연결 : tcp/ip를 통해 커넥션을 연결
2. sql 전달 : 서버가 sql을 연결한 커넥션을 통해 db에 전달
3. 결과 응답 : 그러면 db가 전달된 sql을 수행하고 그 결과를 어플 서버에 응답하고 서버는 응답결과를 활용(메모리 map으로 한 것처럼 화면에 출력하고 할 것입니다.)

 

문제는 각각의 db마다 커넥션을 연결하는 방법, sql을 전달하는 방법, 결과를 응답 받는 방법이 모두 달랐습니다.

 

-> 2가지 문제

1) db를 다른 종류로 바꾸면 서버의 논리 로직을 다 변경해야 했습니다. 서버는 mysql db에 연결하는 방법과 db의 응답 결과의 포멧이 있으니 그것을 처리하는 것이 오라클 db와 다르고 그런 것입니다.

2) 개발자가 각각의 db마다 커넥션 연결, sql 전달, 결과 응답 처리를 새로 학습해야 했습니다.

 

- jdbc 표준 인터페이스

이런 문제를 해결하기 위해 jdbc 자바 표준이 등장합니다. 자바에서 db에 접속할 수 있도록 하는 자바 api로 jdbc는 db에서 자료를 쿼리하거나 업데이트하는 방법을 제공합니다.

그림을 보면 jdbc가 커넥션하는 것, sql 전달하는 것, sql 응답을 다 인터페이스로 정해놔서 db 회사마다 다 그 인터페이스에 맞춰서 개발하게 했습니다. 클라는 이 인터페이스를 보고 사용하면 되고 회사는 이 인터에 맞춰서 개발하면 됐습니다. jdbc 인터를 각각의 db 회사에서 자신의 db에 맞도록 구현해서 라이브러리로 제공하는데, 

 

이것을 jdbc 드라이버라고 합니다. 표준 jdbc에서 제공하는 "1) 연결은 이렇게 해야해(conncection)", "2) statement는 이렇게 생겼어(statement)", "3) resultset은 이렇게 생겼어(resultset)"를 인터페이스의 모양에 맞춰서 구현해서 제공하는데 이것을 jdbc 드라이버라고 하고 예를들어서 mysql db에 접근할 수 있는 것은 mysql jdbc 드라이버, oracle db에 접근할 수 있는 것은 oracle jdbc 드라이버라고 합니다.

 

-> 정리

jdbc의 등장으로 2가지 문제를 해결했습니다.

1. 다른 종류의 db로 바꾸면 서버 로직도 함께 변경해야 했는데 이제는 어플리케이션 로직은 이제 jdbc 표준 인터에만 의존합니다. 따라서 db를 다른 종류의 db로 변경하고 싶으면 jdbc 구현 라이브러리만 변경하면 서버 코드는 그대로 유지 할 수 있습니다.(추상화에 의존)

2. 개발자가 각 db마다 새로 커넥션 연결, sql 전달, 결과 응답 처리를 새로 학습해야 했었는데 이제는 jdbc 표준 인터 사용법만 알면 모든 db에 동일하게 적용할 수 있습니다.

※ 표준화의 한계

편리해졌지만 각각의 db마다 사용법이 다릅니다. mysql과 오라클 문법이 조금씩 다른 것입니다. 결국 db를 변경하면 jdbc 코드는 변경하지 않아도 되지만 sql은 문법은 바꿔야합니다.

 

- jdbc와 최신 데이터 접근 기술

jdbc는 1997년 기술입니다. 그래서 현재는 jdbc를 편리하게 사용할 수 있는 SQL 맵퍼와 ORM 기술로 나뉩니다.

1. jdbc 직접 사용

어플 로직에서 jdbc를 직접 사용해서 sql을 전달하면 됩니다. 그 뜻은 jdbc의 3개의 인터페이스를 어플 로직에서 직접 선언하고 구현체를 생성하는 것입니다. 그러면 jdbc에서 뒤에 db에 연결해서 동작합니다. 근데 이게 굉장히 복잡합니다.

 

2. sql 맵퍼

jdbc를 직접 사용하지 않고 편리하게 사용하도록 도와주는 기술입니다. 어플 로직에서 jdbc로 직접 sql을 전달하지 않고 sql 맵퍼에게 전달합니다. jdbc 탬플릿과 mybatis입니다. jdbc는 sql응답 결과을 객체로 바꿀 때 되게 복잡한데 그런 응답 결과를 객체로 바꾸는 것을 자동화해줍니다. 근데 개발자가 직접 sql을 작성해야합니다.


3. orm 기술

어플 로직에서 직접 sql을 전달하지 않고 회원 객체를 jpa에 전달하면 여기서 직접 sql을 만들어냅니다. 개발자는 컬랙션에 객체 넣듯이 넣으면 jpa가 sql로 만들어줍니다. 또한 오라클과 mysql sql이 다른 것도 중간에서 해결해줍니다.

 

-> 신 기술의 장단점

sql 맵퍼는 sql만 직접 작성하면 맵퍼가 대신 해줍니다. 그래서 sql만 알면 금방 배워서 할 수 있습니다. orm은 sql을 작성하지 않아도 쓸 수 있어서 편리하지만 쉽지 않아 학습이 필요합니다.

 

※ 이런 기술들도 내부에서는 결국 jdbc를 사용하는 것입니다. 따라서 jdbc가 어떻게 동작하는기 기본원리를 알아야합니다. 무엇보다 문제가 발생했을 떄 근본적인 문제를 찾아 해결하기 위해 필수로 알아야하는 근본 기술입니다.

 

- 데이터베이스 연결

서버의 로직과 db를 연결할 것입니다. 커넥션 패키지를 하나 만들고 커넥션 상수를 하나 만듭니다.

 

public abstract class ConnectionConst {
    public static final String URL = "jdbc:h2:tcp://localhost/~/test";
    public static final String USERNAME = "sa";
    public static final String PASSWORD = "";
}

db의 정보를 상수로 저장할 것입니다. h2에 접근하는 방법은 규약으로 나와있습니다. 그것을 URL, USERNAME, PASSWORD를 정해놓습니다.

 

@Slf4j
public class DBConnectionUtil {
    public static Connection getConnection() {
        try {
            Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            log.info("get connection={}, class={}", connection, connection.getClass());
            return connection;
        } catch (SQLException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

이제 DB 연결하는 코드를 만들 util 클래스를 만들고 static으로 커넥션하는 메서드를 작성합니다. 이게 jdbc의 표준 인터페이스의 그 커넥션입니다. 드라이버 매니저의 get커넥션에 URL, 이름, pw 상수를 넣습니다. 예외가 있어서 try로 하고 log로 잘 연결했는지 보겠습니다. 객체 정보와 클래스 타입 정보를 로깅합니다. 객체를 반환하고 catch에서 예외를 톰캣에게 던집니다. db를 사용할 때마다 이 코드를 직접 작성해도 되지만 매번 번거롭기에 메서드로 만든 것 입니다.

 

db에 연결하려면 jdbc가 제공하는 드라이버매니저의 getConnection을 사용하면 됩니다. 이렇게 하면 라이브러리에있는 jdbc db 드라이버를 찾습니다.

 

우리는 h2가 필요하니 라이브러리에 보면 사실 이 Driver 클래스를 통해서 db에 들어가게 됩니다. 드라이버 매니저가 이 드라이버 클래스를 찾습니다. 

 

-> 테스트

@Test
void getConnection() {
    Connection connection = DBConnectionUtil.getConnection();
    assertThat(connection).isNotNull();
}

동작하는지 보기위해 테스트를 해보겠습니다. DB 커넥션 유틸에서 만들어놓은 getconnection 메서드로 커넥션 객체를 가져오고 검증은 만든 커넥션 객체가 null이 아니면 성공입니다.

 

로그를 보면 class와 객체를 찍습니다. getConnection하면  커넥션을 가져오는데 초기화는 부모 클래스인 커넥션으로 합니다.

 

부모 커넥션은 인터페이스라서 get으로 구현체를 가져올텐데 구현체가 뭐냐면 바로 위에서 말한 jdbc 표준 인터페이스는 3가지 커넥션, sql전달, 결과 응답을 규칙으로 정했다고 했고 오라클은 db 연결을 위해 오라클 버전으로 jdbc를 구현한 드라이버안에 오라클 커넥션을 가지고 있는데 h2는 로그의 클래스 정보로 나온 h2.jdbc커넥션을 가지고 옵니다.

 

 

걔가 바로 h2 라이브러리 밑에 jdbc커넥션입니다. 드라이버가 아니고 실제 h2 드라이버 내부의 jdbc 표준 인터페이스의 커넥션을 구현한 커넥션을 가져온 것입니다.(사실 드라이버 매니저가 h2 드라이버를 찾고 h2 드라이버에서 h2 커낵션을 제공하는 것입니다.) 개발자는 그냥 커낵션 인터페이스에 기반해서만 코딩하면 됩니다. db가 바뀌어도 커넥션을 획득하는 방법엔 변화가 없습니다.

 

이전에 jdbc가 한 것이 인터로 커넥션, sql 전달, 응답 결과 처리를 제공한다고 했으니 그 중 하나인 커넥션 구현체입니다. 커넥션 인터페이스 구현체입니다. jdbc 표준 인터이고 그게 커넥션 인터, statement 인터, resultset 인터입니다. 결과로 나온 class=class org.h2.jdbc.JdbcConnection이 H2 db 드라이버가 제공하는 h2 전용 커넥션입니다.

 

드라이버가 jdbc 표준 인터를 구현한 것이고 그 안에는 당연히 구현체가 있을 것인데 jdbc의 인터가 커낵션 포함 3개이니 h2 드라이버가 제공하는 것은 커낵션 포함 3개의 구현체일 것입니다. 개발자는 이 3개의 인터페이스를 사용하도록 로직을 작성하면 되고 db가 바뀌면 jdbc 구현체만 바꾸면 됩니다. 구현체를 바꾼다는 것은 드라이버를 바꾸고 그 안에 구현된 3개를 바꾼다고 생각하면 됩니다.

실제 구현하고 있는지 가보면 아까 get으로 커넥션을 가져오면 받는 부모가 implements에 있고 이것을 구현하는 것으로 봐서 이 Connection 인터가 표준 jdbc의 커넥션 인터입니다.

 

 

- jdbc 드라이버 매니저 연결 이해

h2를 드라이버를 어떻게 찾는 건지 궁금합니다. h2 커넥션이 j 표준 커넥션을 구현했는데 이게 있어야 h2 db와 통신이 되는 것입니다.

 

예를들어 매니저가 관리하는 드라이버 목록에 h2, my가 있다면 얘들이 다 관리가 되는데 get할 때 url을 주고 이름, 비번을 보내면 get에서 관리하는 드라이버 목록에 다 던집니다. 그러면 각 드라이버는 url 정보를 보고 본인이 처리할 수 있는지 확인합니다. 예를들어서 url에 jdbc:h2로 시작하면 h2 db에 접근하기 위한 규칙이라서 h2 드라이버는 자신이 처리할 수 있으므로 실제 db에 연결해서 커넥션을 획득하고 이 커넥션을 클라에 반환합니다.

 

반면에 j:h2로 시작했는데 my 드라이버에 먼저 던지면 자신이 처리할 수 없다를 반환하고 다음순서로 넘어갑니다. 이렇게 찾은 커넥션 구현체가 db와 연결을 한 후에 클라에게 반환이 됩니다. 물론 모든 커넥션은 표준 j의 java.sql.커넥션을 구현하고 있습니다.

 

- jdbc 개발 - 등록

본격적으로 j를 사용해서 어플을 개발해보겠습니다. 먼저 j를 사용해서 회원 데이터를 db에 등록하는 기능을 개발해보겠습니다. 

 

@Data
public class Member {
    private String memberId;
    private int money;

    public Member(String memberId, int money) {
        this.memberId = memberId;
        this.money = money;
    }

    public Member() {
    }
}

도메인 패키지를 하나 만들고 멤버 객체를 만들고 id와 돈을 필드로 가지게 하고 기본 생성자와 초기화 생성자 2개를 만듭니다. 이 2 필드가 db에 들어갈 것입니다. 이것을 j를 직접 사용해서 저장할 것입니다. 예를들어 form으로 member 객체를 만들고 필드에 저장된 값을 가지고 실제 db에 저장할 것입니다. 이후에는 이렇게 직접 j를 쓰지는 않을 것이지만 한 번쯤은 해보는 것이 좋습니다.

 

public class MemberRepositoryV0 {

    public Member save(Member member) throws SQLException {
        String sql = "inset into member(member_id, money) values(?, ?)";

        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = DBConnectionUtil.getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, member.getMemberId());
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate();
            return member;
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            pstmt.close();
            con.close();
        }
    }
}

레포 패키지를 만들고 멤버 레포지토리를 만듭니다. 회원을 저장할 때 사용할 sql을 만듭니다. 그 다음 커넥션을 가져와서 연결할 준비를 하고 statement로 쿼리를 날릴 준비를 합니다. 이게 sql 전달하는 인터페이스입니다.  get으로 커넥션을 가져오고 con.proparestatement(sql)로 sql을 넘기면 statement를 줍니다. 연결에 sql을 담고 pstmt로 전달하는 개념입니다.

 

> 이것을 sql 예외때문에 try로 잡아야합니다. pstmt에 setString으로 쿼리의 values(?, ?)에 파라미터를 넣어줍니다. 1로 첫번째는 id, 2는 두번째로 돈을 넣으면 values에 값이 들어갑니다. setString, int로 타입 정보도 주는 것입니다. 실행을 하면 준비한 쿼리가 실제 db에 sql이 전달되어 실행됩니다. state가 db에 쿼리를 던지는 것이라고 했으니 execute합니다. 그리고 멤버를 반환합니다. 멤버 반환이 원래 save 메서드에서 return하던 것입니다.

 

※ execute하는 것은 DB에서 변환하는 row 수 만큼 int 값을 반환합니다. insert하면 1이 반환되고 update를 하여 10개의 row가 변화가 있으면 10을 반환합니다.

 

+ 예외가 터지면 로그를 날립니다. catch에서는 로그를 남기기 위해 catch를 한 것이고 실제 예외는 톰캣에게 던집니다. 그리고 이게 중요한데 fin에서 con, pstmt를 close를 해야합니다. 역순으로 pstmt 먼저 닫아야하고 실제로 db와 연결을 tcp를 이용해서 하는 것이라서 꼭 닫아야합니다.

  finally {
    close(con, pstmt, null);
}

public void close(Connection con, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.error("error", e);
            }
        }

        if (rs != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                log.error("error", e);
            }
        }

        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                log.error("error", e);
            }
        }
    }

근데 만약에 pstmt에서 close를 하는데 문제가 생기면 con의 close가 실행이 안됩니다. 그래서 try를 또 해야합니다. close 메서드를 만드는데 con, stmt, rs이 3개의 인터를 다 받고 커넥션을 닫아주는 것을 할 건데 위에 2개 닫는 것을 이 메서드에서 다 하고 rs는 지금 없으니 null이라고 합니다. stmt는 sql을 그대로 넣는 것이고 pstmt는 파라미터를 바인딩할 수 있는 것으로 기능이 더 많은 것입니다.

 

> 그리고 close 메서드에서 만약에 stmt가 null이 아니면 닫아주는데 또 예외가 있어서 try를 하고 catch에서 log를 남깁니다. 그리고 con도 null이 아니면 try로 합니다. 이렇게 하여 pstmt가 닫히다가 문제가 생겨도 con이 닫히게 합니다. rs도 지금은 없지만 닫아줘야합니다. 참 j를 직접 쓰는 것이 지저분하고 옛날엔 정말 이렇게 코딩했습니다. 이렇게 하면 등록하는 기능이 완료됩니다.

 

-> 테스트

class MemberRepositoryV0Test {
    MemberRepositoryV0 repository =new MemberRepositoryV0();
    @Test
    void crud() throws SQLException {
        Member member = new Member("memberId", 10000);
        repository.save(member);
    }
}

이제 테스트를 통해서 db에 등록해보겠습니다. 작성한 코드가 잘 동작하나 보겠습니다.

 

레포를 선언하고 멤버를 만들고 id와 만원을 가지게 하고 레포.save하면 db에 저장이 됩니다. 근데 한 번 더 실행하면 예외가 터집니다. Id가 기본키로 되어있어서 그렇습니다.

 

- j 개발 - 조회

public Member findById(String memberId) throws SQLException {
        String sql = "select * from member where member_id = ?";

        Connection con = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, memberId);

            rs = pstmt.executeQuery();
            if (rs.next()) {
                Member member = new Member();
                member.setMemberId(rs.getString("member_id"));
                member.setMoney(rs.getInt("money"));
                return member;
            } else {
                throw new NoSuchElementException("member not found memberId = " + memberId);
            }
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pstmt, rs);
        }
    }

j를 통해 이전에 저장한 데이터를 조회해보겠습니다. findById로 만듭니다. 동일하게 sql을 작성하고 con, pstmt, rs를 선언합니다. 또 try를 열고 con과 pstmt로 sql을 넣고 pstmt에 파라미터를 set하고 실행합니다. select는 execute query를 해야합니다. 이러면 rs를 반환하는데 rs는 select의 결과를 담고 있는 통입니다. rs에서 값을 꺼내야하는데 rs.next()하고 멤버에 데이터를 re.get해서 set하여 멤버를 생성하고 초기화하고 찾은 값으로 멤버를 만들어 반환합니다.

 

> rs는 커서라는 것이 있는데 처음에는 빈 곳을 보고 있습니다. next()하면 한 칸 아래를 보고 거기부터 데이터의 시작입니다. next()해서 있으면 t, 없으면 데이터가 없는 것으로 nosuchelementexception으로 날립니다. 예외가 터지면 메세지를 잘 넣어서 어디서 예외가 터진 것인지 잘 알 수 있게 해야합니다. fin에서 close를 하면 끝입니다.

 

-> 테스트

@Test
void crud() throws SQLException {
    // save
    Member member = new Member("memberId", 10000);
    repository.save(member);

    // findById
    Member findMember = repository.findById(member.getMemberId());
    log.info("findMember={}", findMember);
    assertThat(member).isEqualTo(findMember);
}

등록 테스트에서 저장한 멤버와 같은 것을 찾나 테스트할 것입니다.

로그를 확인해보면 findMember에 저장한 데이터가 나옵니다. Member객체의 @Data에 롬복을 썼기 때문에 예쁘게 결과가 나옵니다. 항상 테스트할 때는 기본키를 생각하고 delete from member하고 해야합니다.

 

- resultSet

rs 결과가 select한 순서대로 결과를 담는데 내부에 커서라는 것이 있어서 다음 데이터를 조회합니다. 처음에는 아무것도 안 가리키고 있어서 next()를 최초 한 번은 호출해야 합니다. 최초의 결과가 t면 rs에 데이터가 있다는 뜻이고 f면 없다는 뜻입니다.

 

> 따라서 지금처럼 where로 데이터가 한 행인 경우는 if next() 한 번만 하면 되는데 select 결과가 여러개면 while문을 해야합니다. 이 경우 마지막 행에서 next()가 f가 되어서 while이 끝납니다. getString은 memberId를 str 타입으로 반환하고 getInt는 int로 반환합니다.

 

- j 개발 - 수정, 삭제

등록, 수정, 삭제는 executeUpdate를 쓰면 됩니다. 

 

-> 수정

public void update(String memberId, int money) throws SQLException {
        String sql = "update member set money=? where member_id=?";
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setInt(1, money);
            pstmt.setString(2, memberId);
            int resultSize = pstmt.executeUpdate();
            log.info("resultSize={}", resultSize);
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pstmt, null);
        }
    }

파라미터로 id와 돈을 받아서 id로 멤버를 찾아서 돈을 수정하는 sql을 짭니다. resultSize를 로그 찍으면 where을 썼기에 무조건 0이나 1이 나올 것입니다.

 

//update
repository.update(member.getMemberId(), 20000);
Member updateMember = repository.findById(member.getMemberId());
assertThat(updateMember.getMoney()).isEqualTo(20000);

테스트에서 20000원으로 바꾸고 findById로 찾아서 20000원인지 봅니다. 

 

실행로그로 size가 1인게 나옵니다.

 

-> 삭제

public void delete(String memberId) throws SQLException {
    String sql = "delete from member where member_id = ?";
    Connection con = null;
    PreparedStatement pstmt = null;
    try {
        con = getConnection();
        pstmt = con.prepareStatement(sql);
        pstmt.setString(1, memberId);
        int resultSize = pstmt.executeUpdate();
        log.info("resultSize={}", resultSize);
    } catch (SQLException e) {
        log.error("db error", e);
        throw e;
    } finally {
        close(con, pstmt, null);
    }
}

id를 받아서 삭제합니다. 보면 수정 코드랑 너무 비슷합니다. 그래서 sql 맵퍼가 중복을 막기 위해서 나온 것입니다.

 

//delete
repository.delete(member.getMemberId());

테스트에서 delete를 하고 눈으로 확인하면 제가 v4를 넣었는데 실제로 없어졌습니다.

 

 

//delete
repository.delete(member.getMemberId());
Member deleteMember = repository.findById(member.getMemberId());

검증은 id로 조회하고 값이 null이면 됩니다. 근데 findById가 멤버 id로 찾는 것인데 그 멤버가 삭제되니 절대로 find할 방법이 없습니다.

 

그래서 위에서 만든 멤버를 찾을 수 없다는 예외가 뜹니다.

 

//delete
repository.delete(member.getMemberId());
assertThatThrownBy(() -> repository.findById(member.getMemberId()))
        .isInstanceOf(NoSuchElementException.class);

삭제는 검증하는 방법이 따로 있습니다. 삭제를 하면 데이터가 사라지는데 그때에 맞춰서 예외가 터지게 해놨습니다. 따라서 이 예외가 터지면 삭제된 것이라고 생각할 수 있습니다. '->' 오른쪽의 코드를 호출하면 예외가 터질 것인데 NoSuchElement 예외가 터질 것이라는 얘기입니다.

 

> 지금 이렇게 db를 계속 delete from으로 삭제하고 그러는데 물론 삭제 메서드를 하면 delete from를 안해도 됩니다. 근데 만약 테스트 코드에서 삭제 테스트 전에 예외가 터지면 db에 데이터가 삭제가 안 될 것입니다. 트랜잭션을 활용하면 이 문제를 깔끔하게 해결할 수 있다.

 

'[백엔드] > [spring | 학습기록]' 카테고리의 다른 글

spring PART.트랜잭션 이해  (0) 2023.05.03
[문법] 커넥션과 데이터 소스  (0) 2023.05.02
spring PART.중간점검 2  (0) 2023.04.30
spring PART.파일 업로드  (0) 2023.04.28
spring PART.API 예외처리  (0) 2023.04.27
Comments