Spring DB Part I

Spring DB Part I

μ˜ν•œλ‹˜μ˜ μŠ€ν”„λ§ DB 1편 - 데이터 μ ‘κ·Ό 핡심 원리 κ°•μ˜λ₯Ό μš”μ•½ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.

Project

Intro

H2 λ°μ΄ν„°λ² μ΄μŠ€ μ„€μ •

Download

μ‹€ν–‰

  • μ‹€ν–‰ κΆŒν•œ: chmod 755 h2.sh

  • μ‹€ν–‰: ./h2.sh

  • mv.db 파일 생성: jdbc:h2:~/test

  • 접속: jdbc:h2:tcp://localhost/~/test

JDBC

Java Database Connectivity

  • μžλ°”μ—μ„œ λ°μ΄ν„°λ² μ΄μŠ€μ— μ ‘μ†ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜λŠ” μžλ°” API

Server <-> DB

  • Connection μ—°κ²°: 주둜 TCP/IPλ₯Ό μ‚¬μš©ν•΄μ„œ 컀λ„₯μ…˜ μ—°κ²°

  • SQL 전달: μ„œλ²„λŠ” DBκ°€ 이해할 수 μžˆλŠ” SQL을 컀λ„₯μ…˜μœΌλ‘œ DB에 전달

  • Response: DBλŠ” μ „λ‹¬λœ SQL을 μˆ˜ν–‰ν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό 응닡 -> μ„œλ²„λŠ” 응닡 κ²°κ³Ό ν™œμš©

JDBC ν‘œμ€€ μΈν„°νŽ˜μ΄μŠ€

  • java.sql.Connection: μ—°κ²°

  • java.sql.Statement: SQL을 담은 λ‚΄μš©

  • java.sql.ResultSet: SQL μš”μ²­ 응닡

JDBC 데이터 μ ‘κ·Ό 기술

  • SQL Mapper

    • Spring JdbcTemplate

    • MyBatis

  • ORM

    • JPA

    • hibernate

    • eclipse link

λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²°

  • JDBCλŠ” java.sql.Connection ν‘œμ€€ 컀λ„₯μ…˜ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜

    • H2 λ°μ΄ν„°λ² μ΄μŠ€ λ“œλΌμ΄λ²„λŠ” JDBC Connection μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œ org.h2.jdbc.JdbcConnection κ΅¬ν˜„μ²΄ 제곡

  • JDBCκ°€ μ œκ³΅ν•˜λŠ” DriverManager λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μ— λ“±λ‘λœ DB λ“œλΌμ΄λ²„λ“€μ„ κ΄€λ¦¬ν•˜κ³ , 컀λ„₯μ…˜μ„ νšλ“ν•˜λŠ” κΈ°λŠ₯ 제곡

.

DriverManager 컀λ„₯μ…˜ μš”μ²­ 흐름

  • μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‘œμ§μ—μ„œ 컀λ„₯μ…˜μ΄ ν•„μš”ν•˜λ©΄ DriverManager.getConnection() 호좜

  • DriverManager λŠ” λΌμ΄λΈŒλŸ¬λ¦¬μ— λ“±λ‘λœ λ“œλΌμ΄λ²„ λͺ©λ‘μ„ μžλ™μœΌλ‘œ 인식

    • λ“œλΌμ΄λ²„λ“€μ—κ²Œ μˆœμ„œλŒ€λ‘œ URL, d이름, λΉ„λ°€λ²ˆν˜Έ λ“± 접속이 ν•„μš”ν•œ 정보λ₯Ό λ„˜κ²¨ 컀λ„₯μ…˜μ„ νšλ“ν•  수 μžˆλŠ”μ§€ 확인

    • 각각의 λ“œλΌμ΄λ²„λŠ” URL 정보λ₯Ό μ²΄ν¬ν•΄μ„œ 본인이 μ²˜λ¦¬ν•  수 μžˆλŠ” μš”μ²­μΈμ§€ 확인

  • 찾은 컀λ„₯μ…˜ κ΅¬ν˜„μ²΄λ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ— λ°˜ν™˜

    • μ²˜λ¦¬κ°€ κ°€λŠ₯ν•œ λ“œλΌμ΄λ²„μ˜ 경우 μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€μ— μ—°κ²°ν•΄μ„œ 컀λ„₯μ…˜μ„ νšλ“ν•˜κ³  이 컀λ„₯μ…˜μ„ ν΄λΌμ΄μ–ΈνŠΈμ— λ°˜ν™˜

    • 반면 URL이 jdbc:h2 둜 μ‹œμž‘ν–ˆλŠ”λ° MySQL λ“œλΌμ΄λ²„κ°€ λ¨Όμ € 싀행될 경우, μ²˜λ¦¬ν•  수 μ—†λ‹€λŠ” κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜κ²Œ 되고, λ‹€μŒ λ“œλΌμ΄λ²„μ—κ²Œ μˆœμ„œκ°€ 전달

commit


getConnection() & close()

DriverManager

@Slf4j
public class MemberRepository {
    private void close(Connection con, Statement stmt, ResultSet rs) {

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

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

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

    private Connection getConnection() {
        return DBConnectionUtil.getConnection();
    }
}

commit

DataSource

@Slf4j
public class MemberRepository {

    private final DataSource dataSource;

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

    private void close(Connection con, Statement stmt, ResultSet rs) {
        JdbcUtils.closeResultSet(rs);
        JdbcUtils.closeStatement(stmt);
        JdbcUtils.closeConnection(con);
    }

    private Connection getConnection() throws SQLException {
        Connection con = dataSource.getConnection();
        log.info("get connection={}, class={}", con, con.getClass());
        return con;
    }
}

commit


등둝

@Slf4j
public class MemberRepository {

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

        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql); // λ°μ΄ν„°λ² μ΄μŠ€μ— 전달할 SQLκ³Ό νŒŒλΌλ―Έν„°λ‘œ 전달할 데이터듀을 μ€€λΉ„
            pstmt.setString(1, member.getMemberId());
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate(); // μ€€λΉ„λœ SQL을 컀λ„₯μ…˜μ„ 톡해 μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€λ‘œ 전달
            return member;
        } catch (SQLException e) {
            log.error("db error", e);
            throw e;
        } finally {
            close(con, pstmt, null);
        }
    }
}

쑰회

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);
    }
}

μˆ˜μ •, μ‚­μ œ

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);
    }
}

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);
        pstmt.executeUpdate();
    } catch (SQLException e) {
        log.error("db error", e);
        throw e;
    } finally {
        close(con, pstmt, null);
    }
}

Connection Pool & DataSource

λ°μ΄ν„°λ² μ΄μŠ€ 컀λ„₯μ…˜ νšλ“ κ³Όμ •

  • μ„œλ²„μ—μ„œ DB λ“œλΌμ΄λ²„λ₯Ό 톡해 컀λ„₯μ…˜ 쑰회

  • DB λ“œλΌμ΄λ²„λŠ” DB와 TCP/IP 컀λ„₯μ…˜ μ—°κ²° (3 way handshake λ™μž‘ λ°œμƒ)

  • TCP/IP 컀λ„₯μ…˜μ΄ μ—°κ²°λ˜λ©΄, ID/PW와 기타 뢀가정보λ₯Ό DB에 전달

  • DBλŠ” ID/PWλ₯Ό 톡해 λ‚΄λΆ€ 인증을 μ™„λ£Œν•˜κ³ , λ‚΄λΆ€ DB μ„Έμ…˜ 생성

  • DBλŠ” 컀λ„₯μ…˜ 생성이 μ™„λ£Œλ˜μ—ˆλ‹€λŠ” 응닡 전달

  • DB λ“œλΌμ΄λ²„λŠ” 컀λ„₯μ…˜ 객체λ₯Ό μƒμ„±ν•΄μ„œ ν΄λΌμ΄μ–ΈνŠΈμ— λ°˜ν™˜

ConnectionPool

컀λ„₯μ…˜μ„ κ΄€λ¦¬ν•˜λŠ” 수영μž₯(!)

  • DriverManager λ₯Ό 톡해 λ°μ΄ν„°λ² μ΄μŠ€ 컀λ„₯μ…˜μ„ 맀번 μƒˆλ‘œ μƒμ„±ν•˜λŠ” κ³Όμ •μ—μ„œ λ°œμƒν•˜λŠ” 응닡 속도 μ €ν•˜ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ 컀λ„₯μ…˜μ„ 미리 생성해두고 μ‚¬μš©

ConnectionPool μ΄ˆκΈ°ν™”

  • μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹œμž‘ μ‹œμ μ— ν•„μš”ν•œ 만큼의 컀λ„₯μ…˜μ„ 미리 ν™•λ³΄ν•΄μ„œ 풀에 보관

    • 기본값은 보톡 10개

ConnectionPool μ—°κ²° μƒνƒœ

  • 컀λ„₯μ…˜ 풀에 λ“€μ–΄ μžˆλŠ” 컀λ„₯μ…˜μ€ TCP/IP둜 DB와 컀λ„₯μ…˜μ΄ μ—°κ²°λ˜μ–΄ μžˆλŠ” μƒνƒœ

    • μ–Έμ œλ“ μ§€ SQL을 DB에 전달 κ°€λŠ₯

ConnectionPool μ‚¬μš©

  • 컀λ„₯μ…˜ 풀을 톡해 이미 μƒμ„±λ˜μ–΄ μžˆλŠ” 컀λ„₯μ…˜μ„ 객체 참쑰둜 μ–»μ–΄μ„œ μ‚¬μš©

  • 컀λ„₯μ…˜μ„ μš”μ²­ν•˜λ©΄ 컀λ„₯μ…˜ 풀은 μžμ‹ μ΄ 가지고 μžˆλŠ” 컀λ„₯μ…˜ 쀑 ν•˜λ‚˜λ₯Ό λ°˜ν™˜

  • 컀λ„₯μ…˜ ν’€λ‘œλΆ€ν„° 받은 컀λ„₯μ…˜μ„ μ‚¬μš©ν•΄μ„œ SQL을 DB에 μ „λ‹¬ν•˜κ³ , κ·Έ κ²°κ³Όλ₯Ό λ°›μ•„μ„œ 처리

  • 컀λ„₯μ…˜μ„ λͺ¨λ‘ μ‚¬μš©ν•˜λ©΄ 컀λ„₯μ…˜μ„ μ’…λ£Œν•˜μ§€ μ•Šκ³  λ‹€μ‹œ μ‚¬μš©ν•  수 μžˆλ„λ‘ 컀λ„₯μ…˜ 풀에 λ°˜ν™˜

DataSource

컀λ„₯μ…˜μ„ νšλ“ν•˜λŠ” 방법을 좔상화 ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€

  • 컀λ„₯μ…˜ ν’€ μ˜€ν”ˆμ†ŒμŠ€ commons-dbcp2, tomcat-jdbc pool, HikariCP에 직접 μ˜μ‘΄ν•˜λŠ” 것이 μ•„λ‹ˆλΌ, DataSource μΈν„°νŽ˜μ΄μŠ€μ—λ§Œ μ˜μ‘΄ν•˜λ©΄ λœλ‹€!

DriverManager

DriverManager

  • 컀λ„₯μ…˜μ„ νšλ“ν•  λ•Œ λ§ˆλ‹€ URL/USERNAME/PASSWORD λ₯Ό νŒŒλΌλ―Έν„°λ‘œ 계속 전달

DataSourceDriverManager

  • 반면, 처음 객체λ₯Ό 생성할 λ•Œλ§Œ ν•„μš”ν•œ νŒŒλ¦¬λ―Έν„°λ₯Ό λ„˜κΈ°κ³ , 컀λ„₯μ…˜μ„ νšλ“ν•  λ•ŒλŠ” λ‹¨μˆœνžˆ dataSource.getConnection() 만 호좜

  • μ„€μ •κ³Ό μ‚¬μš©μ˜ 뢄리가 λͺ…ν™•

@Test
void driverManager() throws SQLException {
    Connection con1 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    Connection con2 = DriverManager.getConnection(URL, USERNAME, PASSWORD);
    log.info("connection={}, class={}", con1, con1.getClass());
    log.info("connection={}, class={}", con2, con2.getClass());
}

@Test
void dataSourceDriverManager() throws SQLException {
    DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
    useDataSource(dataSource);
}

private void useDataSource(DataSource dataSource) throws SQLException {
    Connection con1 = dataSource.getConnection();
    Connection con2 = dataSource.getConnection();
    log.info("connection={}, class={}", con1, con1.getClass());
    log.info("connection={}, class={}", con2, con2.getClass());
}

Connection Pool

  • 컀λ„₯μ…˜ 풀은 λ³„λ„μ˜ μ“°λ ˆλ“œ μ‚¬μš©ν•΄μ„œ 컀λ„₯μ…˜ 풀에 컀λ„₯μ…˜μ„ μ±„μš΄λ‹€.

  • DriverManagerDataSource λŠ” 항상 μƒˆλ‘œμš΄ 컀λ„₯μ…˜μ„ μƒμ„±ν•˜λŠ” 반면, 컀λ„₯μ…˜ 풀은 컀λ„₯μ…˜μ„ μž¬μ‚¬μš©

@Test
void dataSourceConnectionPool() throws SQLException, InterruptedException {
    HikariDataSource dataSource = new HikariDataSource();
    dataSource.setJdbcUrl(URL);
    dataSource.setUsername(USERNAME);
    dataSource.setPassword(PASSWORD);
    dataSource.setMaximumPoolSize(10);
    dataSource.setPoolName("MyPool");
    
    useDataSource(dataSource);
    Thread.sleep(1000); // 컀λ„₯μ…˜ 생성 μ‹œκ°„ λŒ€κΈ°
}

HikariCP

Transaction

DBμ—μ„œ νŠΈλžœμž­μ…˜μ€ ν•˜λ‚˜μ˜ μž‘μ—…λ₯Ό μ•ˆμ „ν•˜κ²Œ μ²˜λ¦¬ν•˜λ„λ‘ 보μž₯

  • 컀밋(Commit) : λͺ¨λ“  μž‘μ—…μ΄ μ„±κ³΅ν•΄μ„œ DB에 정상 λ°˜μ˜ν•˜λŠ” 것

  • λ‘€λ°±(Rollback) : μž‘μ—…μ΄ ν•˜λ‚˜λΌλ„ μ‹€νŒ¨ν•΄μ„œ μž‘μ—… μ΄μ „μœΌλ‘œ λ˜λŒλ¦¬λŠ” 것

νŠΈλžœμž­μ…˜ ACID

νŠΈλžœμž­μ…˜μ€ μ›μžμ„±(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)을 보μž₯ν•΄μ•Ό ν•œλ‹€.

  • μ›μžμ„±(Atomicity) : νŠΈλžœμž­μ…˜ λ‚΄μ—μ„œ μ‹€ν–‰ν•œ μž‘μ—…λ“€μ€ 마치 ν•˜λ‚˜μ˜ μž‘μ—…μΈ κ²ƒμ²˜λŸΌ λͺ¨λ‘ 성곡 ν•˜κ±°λ‚˜ λͺ¨λ‘ μ‹€νŒ¨ν•΄μ•Ό ν•œλ‹€.

  • 일관성(Consistency) : λͺ¨λ“  νŠΈλžœμž­μ…˜μ€ 일관성 μžˆλŠ” λ°μ΄ν„°λ² μ΄μŠ€ μƒνƒœλ₯Ό μœ μ§€ν•΄μ•Ό ν•œλ‹€.

  • λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ •ν•œ 무결성 μ œμ•½ 쑰건을 항상 λ§Œμ‘±ν•΄μ•Ό ν•œλ‹€.

  • 격리성(Isolation) : λ™μ‹œμ— μ‹€ν–‰λ˜λŠ” νŠΈλžœμž­μ…˜λ“€μ΄ μ„œλ‘œμ—κ²Œ 영ν–₯을 λ―ΈμΉ˜μ§€ μ•Šλ„λ‘ κ²©λ¦¬ν•œλ‹€.

    • λ™μ‹œμ— 같은 데이터λ₯Ό μˆ˜μ •ν•˜μ§€ λͺ»ν•˜λ„둝 ν•΄μ•Ό ν•œλ‹€.

    • νŠΈλžœμž­μ…˜ 간에 격리성을 μ™„λ²½νžˆ 보μž₯ν•˜λ €λ©΄ νŠΈλžœμž­μ…˜μ„ 거의 μˆœμ„œλŒ€λ‘œ μ‹€ν–‰ν•΄μ•Ό ν•˜λ―€λ‘œ ANSI ν‘œμ€€μ€ νŠΈλžœμž­μ…˜μ˜ 격리 μˆ˜μ€€μ„ 4λ‹¨κ³„λ‘œ λ‚˜λˆ„μ–΄ μ •μ˜

    • 격리성은 λ™μ‹œμ„±κ³Ό κ΄€λ ¨λœ μ„±λŠ₯ 이슈둜 인해 νŠΈλžœμž­μ…˜ 격리 μˆ˜μ€€(Isolation level)을 선택할 수 μžˆλ‹€.

      • READ UNCOMMITED(μ»€λ°‹λ˜μ§€ μ•Šμ€ 읽기)

      • READ COMMITTED(μ»€λ°‹λœ 읽기)

      • REPEATABLE READ(반볡 κ°€λŠ₯ν•œ 읽기)

      • SERIALIZABLE(직렬화 κ°€λŠ₯)

  • 지속성(Durability) : νŠΈλžœμž­μ…˜μ„ μ„±κ³΅μ μœΌλ‘œ 끝내면 κ·Έ κ²°κ³Όκ°€ 항상 κΈ°λ‘λ˜μ–΄μ•Ό ν•œλ‹€.

    • 쀑간에 μ‹œμŠ€ν…œμ— λ¬Έμ œκ°€ λ°œμƒν•΄λ„ λ°μ΄ν„°λ² μ΄μŠ€ 둜그 등을 μ‚¬μš©ν•΄μ„œ μ„±κ³΅ν•œ νŠΈλžœμž­μ…˜ λ‚΄μš©μ„ 볡ꡬ해야 ν•œλ‹€.

νŠΈλžœμž­μ…˜ ACID

@Transactional 잘 μ‚¬μš©ν•΄λ³΄κΈ°

νŠΈλžœμž­μ…˜μ˜ μ‚¬μš© μ˜ˆμ‹œ

  • 데이터 λ³€κ²½ 쿼리λ₯Ό μ‹€ν–‰ν•˜κ³  λ°μ΄ν„°λ² μ΄μŠ€μ— κ²°κ³Όλ₯Ό λ°˜μ˜ν•˜λ €λ©΄ commit 을 ν˜ΈμΆœν•˜κ³ ,

  • κ²°κ³Όλ₯Ό λ°˜μ˜ν•˜κ³  싢지 μ•Šλ‹€λ©΄ rollback 을 호좜

  • 컀밋을 ν˜ΈμΆœν•˜κΈ° μ „κΉŒμ§€λŠ” μž„μ‹œλ‘œ 데이터λ₯Ό μ €μž₯ -> ν•΄λ‹Ή νŠΈλžœμž­μ…˜μ„ μ‹œμž‘

μžλ™μ»€λ°‹κ³Ό μˆ˜λ™μ»€λ°‹

μžλ™ 컀밋

  • 각각의 쿼리 μ‹€ν–‰ 직후 μžλ™μœΌλ‘œ 컀밋 호좜

  • μ»€λ°‹μ΄λ‚˜ 둀백을 직접 ν˜ΈμΆœν•˜μ§€ μ•Šμ•„λ„ λ˜λŠ” νŽΈλ¦¬ν•¨

  • ν•˜μ§€λ§Œ, μ›ν•˜λŠ” νŠΈλžœμž­μ…˜ κΈ°λŠ₯을 μ œλŒ€λ‘œ μ‚¬μš©ν•  수 μ—†λŠ” 단점 쑴재

set autocommit true; -- default

μˆ˜λ™ 컀밋

  • μˆ˜λ™ 컀밋 λͺ¨λ“œλ‘œ μ„€μ •ν•˜λŠ” 것이 νŠΈλžœμž­μ…˜ μ‹œμž‘

  • 이후 commit, rollback 호좜 ν•„μš”

  • μˆ˜λ™/μžλ™ 컀밋 λͺ¨λ“œλŠ” ν•œλ²ˆ μ„€μ •ν•˜λ©΄ ν•΄λ‹Ή μ„Έμ…˜μ—μ„œ 계속 μœ μ§€ (쀑간 변경도 κ°€λŠ₯)

set autocommit false;
-- ...
commit;

Lock

  • μ„Έμ…˜μ΄ νŠΈλžœμž­μ…˜μ„ μ‹œμž‘ν•˜κ³  데이터λ₯Ό μˆ˜μ •ν•˜λŠ” λ™μ•ˆ 컀밋 or λ‘€λ°± μ „κΉŒμ§€ λ‹€λ₯Έ μ„Έμ…˜μ—μ„œ ν•΄λ‹Ή 데이터λ₯Ό μˆ˜μ •ν•  수 없도둝 락을 제곡

  • λ‹€λ₯Έ μ„Έμ…˜μ€ 락을 νšλ“ν•  λ•ŒκΉŒμ§€ λŒ€κΈ°

    • 락 λŒ€κΈ° μ‹œκ°„μ„ λ„˜μ–΄κ°€λ©΄ 락 νƒ€μž„μ•„μ›ƒ 였λ₯˜ λ°œμƒ(락 λŒ€κΈ° μ‹œκ°„μ„ μ„€μ • κ°€λŠ₯)

Lock Timeout μ‹œκ°„ μ„€μ •

SET LOCK_TIMEOUT <milliseconds>

Lock Timeout Error

Timeout trying to lock table {0}; SQL statement:
...

μ‘°νšŒμ™€ 락

  • 일반적인 μ‘°νšŒλŠ” 락을 μ‚¬μš©ν•˜μ§€ μ•Šμ§€λ§Œ,

  • 락을 νšλ“ν•΄μ„œ 변경을 막고 μ‹Άλ‹€λ©΄, select .. for update ꡬ문을 μ‚¬μš©

    • νŠΈλžœμž­μ…˜ μ’…λ£Œ μ‹œμ κΉŒμ§€ ν•΄λ‹Ή 데이터λ₯Ό λ‹€λ₯Έ κ³³μ—μ„œ λ³€κ²½ν•˜μ§€ λͺ»ν•˜λ„둝 κ°•μ œλ‘œ 막아야 ν•  경우 μ‚¬μš©

    • ν•΄λ‹Ή μ„Έμ…˜μ΄ 쑰회 μ‹œμ μ— 락을 가져가버리기 λ•Œλ¬Έμ— λ‹€λ₯Έ μ„Έμ…˜μ—μ„œ ν•΄λ‹Ή 데이터λ₯Ό λ³€κ²½ν•  수 μ—†λ‹€(νŠΈλžœμž­μ…˜ 컀밋 μ‹œ 락 λ°˜λ‚©)

κ³Όκ±° νŠΈλžœμž­μ…˜ 적용

  • νŠΈλžœμž­μ…˜μ€ μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œλΆ€ν„° μ‹œμž‘

    • λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 잘λͺ»λ˜λ©΄ λ¬Έμ œκ°€ λ˜λŠ” 뢀뢄을 ν•¨κ»˜ λ‘€λ°±ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

  • νŠΈλžœμž­μ…˜μ„ μ‹œμž‘ν•˜λ €λ©΄ 컀λ„₯μ…˜μ΄ ν•„μš”. set autocommit false;

  • 같은 μ„Έμ…˜μ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜λŠ” λ™μ•ˆ 같은 컀λ„₯μ…˜μ„ μœ μ§€ν•΄μ•Ό ν•œλ‹€.

    • κ°€μž₯ λ‹¨μˆœν•œ 방법은 컀λ„₯μ…˜μ„ νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬ν•˜λŠ” 방법

  • κ³Όκ±° μ„œλ²„μ—μ„œμ˜ νŠΈλžœμ μ…˜ μ μš©μ€ μ„œλΉ„μŠ€ 계측이 맀우 지저뢄해지고 생각보닀 맀우 λ³΅μž‘ν•œ μ½”λ“œλ₯Ό μš”κ΅¬..

commit


κΈ°μ‘΄ νŠΈλžœμž­μ…˜μ˜ 문제점


Transaction Problem

Spring Transaction Manager

νŠΈλžœμž­μ…˜ 좔상화

  • PlatformTransactionManager interface

    • JdbcTransactionManager

    • JpaTransactionManager

    • HibernateTransactionManager

    • EtcTransactionManager

public interface PlatformTransactionManager extends TransactionManager {

	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

	void commit(TransactionStatus status) throws TransactionException;

	void rollback(TransactionStatus status) throws TransactionException;
}

λ¦¬μ†ŒμŠ€ 동기화

  • νŠΈλžœμž­μ…˜μ„ μœ μ§€ν•˜κΈ° μœ„ν•΄ νŠΈλžœμž­μ…˜μ˜ μ‹œμž‘λΆ€ν„° λκΉŒμ§€ 같은 λ°μ΄ν„°λ² μ΄μŠ€ 컀λ„₯μ…˜μ„ μœ μ§€ν•΄μ•Ό ν•œλ‹€.

    • κ³Όκ±°μ—λŠ” νŒŒλΌλ―Έν„°λ‘œ 컀λ„₯μ…˜μ„ μ „λ‹¬ν–ˆμ§€λ§Œ

    • μŠ€ν”„λ§μ€ org.springframework.transaction.support.TransactionSynchronizationManagerλ₯Ό 톡해 ThreadLocal둜 컀λ„₯μ…˜μ„ 동기화

      • TransactionManagerλŠ” λ‚΄λΆ€μ—μ„œ TransactionSynchronizationManagerλ₯Ό μ‚¬μš©ν•˜κ³ , TransactionManagerλ₯Ό 톡해 컀λ„₯μ…˜μ„ νšλ“

      • ThreadLocal을 μ‚¬μš©ν•΄μ„œ λ©€ν‹°μ“°λ ˆλ“œ 상황에 μ•ˆμ „ν•˜κ²Œ 컀λ„₯μ…˜μ„ 동기화가 κ°€λŠ₯

.

  • λ™μž‘ 방식

    • 1.TransactionManagerλŠ” dataSourceλ₯Ό 톡해 컀λ„₯μ…˜μ„ λ§Œλ“€κ³  νŠΈλžœμž­μ…˜ μ‹œμž‘

    • 2.TransactionManagerλŠ” νŠΈλžœμž­μ…˜μ΄ μ‹œμž‘λœ 컀λ„₯μ…˜μ„ TransactionSynchronizationManager에 보관

    • 3.RepositoryλŠ” TransactionSynchronizationManager에 λ³΄κ΄€λœ 컀λ„₯μ…˜μ„ κΊΌλ‚΄μ„œ μ‚¬μš©

    • 4.νŠΈλžœμž­μ…˜μ΄ μ’…λ£Œλ˜λ©΄ TransactionManagerλŠ” TransactionSynchronizationManager에 λ³΄κ΄€λœ 컀λ„₯μ…˜μ„ 톡해 νŠΈλžœμž­μ…˜μ„ μ’…λ£Œν•˜κ³ , 컀λ„₯μ…˜λ„ λ‹«μŒ

TransactionManager

νŠΈλžœμž­μ…˜ 동기화λ₯Ό μ‚¬μš©ν•˜λ €λ©΄ DataSourceUtilsλ₯Ό μ‚¬μš©

DataSourceUtils.getConnection()

private Connection getConnection() throws SQLException {
    Connection con = DataSourceUtils.getConnection(dataSource);
    return con;
}
  • TransactionSynchronizationManagerκ°€ κ΄€λ¦¬ν•˜λŠ” 컀λ„₯μ…˜μ΄ 있으면 ν•΄λ‹Ή 컀λ„₯μ…˜μ„ λ°˜ν™˜

  • 컀λ„₯μ…˜μ΄ μ—†λŠ” 경우 μƒˆλ‘œμš΄ 컀λ„₯μ…˜μ„ μƒμ„±ν•΄μ„œ λ°˜ν™˜

DataSourceUtils.releaseConnection()

private void close(Connection con, Statement stmt, ResultSet rs) {
    //...
    DataSourceUtils.releaseConnection(con, dataSource);
}
  • νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ λ™κΈ°ν™”λœ 컀λ„₯μ…˜μ€ 컀λ„₯μ…˜μ„ 닫지 μ•Šκ³  κ·ΈλŒ€λ‘œ μœ μ§€

  • TransactionSynchronizationManagerκ°€ κ΄€λ¦¬ν•˜λŠ” 컀λ„₯μ…˜μ΄ μ—†λŠ” 경우 ν•΄λ‹Ή 컀λ„₯μ…˜μ„ λ‹«μŒ

  • commit(status), rollback(status) 호좜 μ‹œ μ•Œμ•„μ„œ release μˆ˜ν–‰

commit

Transaction Template

ν…œν”Œλ¦Ώ 콜백 νŒ¨ν„΄ μ μš©μ„ μœ„ν•΄ TransactionTemplate ν…œν”Œλ¦Ώ 클래슀 μž‘μ„±

  • Transaction의 λ°˜λ³΅λ˜λŠ” try, catch, finally μ½”λ“œ 제거

  • 단, μ„œλΉ„μŠ€ λ‘œμ§μ— νŠΈλžœμž­μ…˜ 처리 μ½”λ“œκ°€ ν¬ν•¨λ˜μ–΄ μžˆλŠ” 단점이 쑴재

public class TransactionTemplate {
    private PlatformTransactionManager transactionManager;

    // 응닡값이 μžˆμ„ 경우 μ‚¬μš©
    public <T> T execute(TransactionCallback<T> action) {..}
    // 응닡값이 없을 경우 μ‚¬μš©
    void executeWithoutResult(Consumer<TransactionStatus> action) {..}
}

commit

Transaction AOP

TransactionalProxy λ„μž…μ„ 톡해 νŠΈλžœμž­μ…˜ 처리 객체와 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 처리 μ„œλΉ„μŠ€ 객체λ₯Ό λͺ…ν™•ν•˜κ²Œ 뢄리

  • @Transactional을 νŠΈλžœμž­μ…˜ μ²˜λ¦¬κ°€ ν•„μš”ν•œ 곳에 μΆ”κ°€ν•΄μ£Όλ©΄, μŠ€ν”„λ§μ˜ νŠΈλžœμž­μ…˜ AOPκ°€ νŠΈλžœμž­μ…˜μ΄ 적용된 ν”„λ‘μ‹œλ₯Ό μƒμ„±ν•˜κ³  μžλ™μœΌλ‘œ νŠΈλžœμž­μ…˜ 처리

  • TransactionalProxyλ₯Ό λ„μž…ν•˜λ©΄ @Transactional이 λΆ™μ–΄ μžˆλŠ” λ©”μ„œλ“œλ‚˜ ν΄λž˜μŠ€μ— Spring이 ν•΄λ‹Ή μ„œλΉ„μŠ€ λ‘œμ§μ„ μƒμ†λ°›μ•„μ„œ μžλ™μœΌλ‘œ νŠΈλžœμž­μ…˜ μ½”λ“œλ₯Ό 생성

    • xxxService$$EnhancerBySpringCGLIB$$..

commit

νŠΈλžœμž­μ…˜ AOP λ™μž‘ 흐름

  1. Transaction이 적용된 클래슀/λ©”μ„œλ“œ 호좜

  2. Transaction이 적용된 Spring AOP Proxy 호좜

  3. Spring Container에 λ“±λ‘λœ Transaction Manager νšλ“

  4. νŠΈλžœμž­μ…˜ μ‹œμž‘. transactionManager.getTransaction()

  5. transactionManagerλŠ” λ‚΄λΆ€μ—μ„œ DataSourceλ₯Ό μ‚¬μš©ν•΄ 컀λ„₯μ…˜ 생성

  6. 컀λ„₯μ…˜μ„ μˆ˜λ™ 컀밋 λͺ¨λ“œλ‘œ λ³€κ²½ν•΄μ„œ μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€ νŠΈλžœμž­μ…˜ μ‹œμž‘. setAutoCommit(false)

  7. 컀λ„₯μ…˜μ„ TransactionSynchronizationManager에 보관

  8. TransactionSynchronizationManagerλŠ” ThreadLocal에 컀λ„₯μ…˜μ„ 보관

    • ThreadLocal: λ©€ν‹° μ“°λ ˆλ“œ ν™˜κ²½μ—μ„œλ„ μ•ˆμ „ν•˜κ²Œ 컀λ„₯μ…˜ 보관

  9. Spring AOP Proxyμ—μ„œ μ‹€μ œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μ‹€ν–‰ν•˜λ©΄μ„œ λ¦¬ν¬μ§€ν† λ¦¬μ˜ λ©”μ„œλ“œλ“€μ„ 호좜 (컀λ„₯μ…˜μ„ νŒŒλΌλ―Έν„°λ‘œ 전달할 ν•„μš”κ°€ 없어짐)

  10. λ¦¬ν¬μ§€ν† λ¦¬λŠ” DataSourceUtils.getConnection()을 톡해 TransactionSynchronizationManager에 λ³΄κ΄€λœ 컀λ„₯μ…˜μ„ κΊΌλ‚΄μ„œ μ‚¬μš©

    • 같은 컀λ„₯μ…˜μ„ μ‚¬μš©ν•˜κ³ , νŠΈλžœμž­μ…˜λ„ μœ μ§€

  11. νšλ“ν•œ 컀λ„₯μ…˜μ„ μ‚¬μš©ν•΄μ„œ SQL을 λ°μ΄ν„°λ² μ΄μŠ€μ— 전달 및 μ‹€ν–‰

  12. λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 λλ‚˜κ³  νŠΈλžœμž­μ…˜μ„ μ’…λ£Œλ₯Ό μœ„ν•΄ TransactionSynchronizationManagerλ₯Ό ν†΅ν•œ λ™κΈ°ν™”λœ 컀λ„₯μ…˜μ„ νšλ“

    • νšλ“ν•œ 컀λ„₯μ…˜μ„ 톡해 컀밋/λ‘€λ°± ν›„ νŠΈλžœμž­μ…˜ μ’…λ£Œ

  13. 전체 λ¦¬μ†ŒμŠ€(TransactionSynchronizationManager, ThreadLocal, setAutoCommit(true), con.close()..) 정리

SpringBoot μžλ™ λ¦¬μ†ŒμŠ€ 등둝

κΈ°μ‘΄μ—λŠ” λ°μ΄ν„°μ†ŒμŠ€μ™€ νŠΈλžœμž­μ…˜ λ§€λ‹ˆμ €λ₯Ό XML둜 λ“±λ‘ν•˜κ±°λ‚˜ 직접 μŠ€ν”„λ§ 빈으둜 등둝해야 ν–ˆμ§€λ§Œ, SpringBootλ₯Ό 톡해 λ§Žμ€ 뢀뢄이 μžλ™ν™”

μžλ™ 등둝

DataSource

  • application.properties에 μžˆλŠ” 속성을 μ‚¬μš©ν•΄μ„œ DataSourceλ₯Ό μƒμ„±ν•˜κ³  μŠ€ν”„λ§ λΉˆμ— μžλ™μœΌλ‘œ 등둝

    • 직접 DataSourceλ₯Ό 빈으둜 λ“±λ‘ν•˜λ©΄ μŠ€ν”„λ§ λΆ€νŠΈλŠ” μžλ™μœΌλ‘œ λ“±λ‘ν•˜μ§€ μ•ŠμŒ

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
spring.datasource.password=

TransactionManager

  • μŠ€ν”„λ§ λΆ€νŠΈλŠ” μ μ ˆν•œ νŠΈλžœμž­μ…˜ λ§€λ‹ˆμ €(PlatformTransactionManager)λ₯Ό μžλ™μœΌλ‘œ μŠ€ν”„λ§ λΉˆμ— 등둝

    • μžλ™ 등둝 μŠ€ν”„λ§ 빈 이름: transactionManager

    • DataSource와 λ§ˆμ°¬κ°€μ§€λ‘œ 직접 TransactionManagerλ₯Ό 빈으둜 λ“±λ‘ν•˜λ©΄ μŠ€ν”„λ§ λΆ€νŠΈλŠ” μžλ™μœΌλ‘œ λ“±λ‘ν•˜μ§€ μ•ŠμŒ

  • μžλ™μœΌλ‘œ λ“±λ‘λ˜λŠ” νŠΈλžœμž­μ…˜ λ§€λ‹ˆμ €λŠ” ν˜„μž¬ λ“±λ‘λœ 라이브러리λ₯Ό 보고 νŒλ‹¨

    • JDBC: DataSourceTransactionManager

    • JPA: JpaTransactionManager

    • JDBC + JPA: JpaTransactionManager

@Slf4j
@SpringBootTest
class MemberServiceV3_4Test {

    @TestConfiguration
    static class TestConfig {

        private final DataSource dataSource;

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

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

        @Bean
        MemberServiceV3_3 memberServiceV3_3() {
            return new MemberServiceV3_3(memberRepositoryV3());
        }
    }
}
  • SpringBootκ°€ application.properties에 μ§€μ •λœ 속성을 μ°Έκ³ ν•΄μ„œ λ°μ΄ν„°μ†ŒμŠ€μ™€ νŠΈλžœμž­μ…˜ λ§€λ‹ˆμ €λ₯Ό μžλ™μœΌλ‘œ 생성

  • μƒμ„±μžλ₯Ό 톡해 SpringBootκ°€ λ§Œλ“€μ–΄μ€€ λ°μ΄ν„°μ†ŒμŠ€ λΉˆμ„ μ£Όμž… κ°€λŠ₯

직접 등둝

@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());
    }
}

Configure a DataSource

Common Application Properties

Java Excaption

  • Object : λͺ¨λ“  객체의 μ΅œμƒμœ„ λΆ€λͺ¨

  • Throwable : μ΅œμƒμœ„ μ˜ˆμ™Έ

    • μƒμœ„ μ˜ˆμ™Έλ₯Ό 작으면 κ·Έ ν•˜μœ„ μ˜ˆμ™Έ(Error..)κΉŒμ§€ ν•¨κ»˜ μž‘μœΌλ―€λ‘œ, Throwable μ˜ˆμ™ΈλŠ” μž‘μ§€ 말고, ExceptionλΆ€ν„° 작자.

  • Error : μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ 볡ꡬ λΆˆκ°€λŠ₯ν•œ μ‹œμŠ€ν…œ μ˜ˆμ™Έ (λ©”λͺ¨λ¦¬ λΆ€μ‘±μ΄λ‚˜ μ‹¬κ°ν•œ μ‹œμŠ€ν…œ 였λ₯˜)

    • unchecked exception

  • Exception : μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ‘œμ§μ—μ„œ μ‚¬μš©ν•  수 μžˆλŠ” μ‹€μ§ˆμ μΈ μ΅œμƒμœ„ μ˜ˆμ™Έ

    • Exceptionκ³Ό κ·Έ ν•˜μœ„ μ˜ˆμ™ΈλŠ” λͺ¨λ‘ μ»΄νŒŒμΌλŸ¬κ°€ μ²΄ν¬ν•˜λŠ” checked exception

    • μ»΄νŒŒμΌλŸ¬κ°€ 체크해 μ£ΌκΈ° λ•Œλ¬Έμ— μž‘κ±°λ‚˜ λ˜μ§€κ±°λ‚˜ ν•˜λ‚˜λ₯Ό ν•„μˆ˜λ‘œ 선택

    • 단, RuntimeException은 μ˜ˆμ™Έ

  • RuntimeException : μ»΄νŒŒμΌλŸ¬κ°€ μ²΄ν¬ν•˜μ§€ μ•ŠλŠ” unchecked exception

    • RuntimeExceptionκ³Ό κ·Έ μžμ‹ μ˜ˆμ™ΈλŠ” λͺ¨λ‘ unchecked exception

μ˜ˆμ™Έμ˜ κΈ°λ³Έ κ·œμΉ™

  • μ˜ˆμ™ΈλŠ” μž‘μ•„μ„œ μ²˜λ¦¬ν•˜κ±°λ‚˜ λ˜μ Έμ•Ό 함.

  • μ˜ˆμ™Έλ₯Ό μž‘κ±°λ‚˜ 던질 λ•Œ μ§€μ •ν•œ μ˜ˆμ™ΈλΏλ§Œ μ•„λ‹ˆλΌ μžμ‹ μ˜ˆμ™Έλ“€λ„ ν•¨κ»˜ 처리

.

  • μ˜ˆμ™Έ 작기 try-catch

    • Repository μ˜ˆμ™Έ λ°œμƒ -> Service둜 μ˜ˆμ™Έ throws -> Serviceμ—μ„œ μ˜ˆμ™Έ 처리 -> 이후 정상 νλ¦„μœΌλ‘œ λ™μž‘

  • μ˜ˆμ™Έ λ˜μ§€κΈ° throws Exception

    • Repository μ˜ˆμ™Έ λ°œμƒ -> Service둜 μ˜ˆμ™Έ throws -> Controller둜 μ˜ˆμ™Έ throws

    • μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜μ§€ λͺ»ν•˜κ³  계속 λ˜μ§€λ©΄ main() μ“°λ ˆλ“œμ˜ 경우 μ˜ˆμ™Έ 둜그λ₯Ό 좜λ ₯ν•˜λ©΄μ„œ μ‹œμŠ€ν…œμ΄ μ’…λ£Œλ˜κ³ , μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 경우 WASκ°€ ν•΄λ‹Ή μ˜ˆμ™Έλ₯Ό λ°›μ•„μ„œ μ²˜λ¦¬ν•˜λŠ”λ°, 주둜 μ‚¬μš©μžμ—κ²Œ μ§€μ •ν•œ 였λ₯˜ νŽ˜μ΄μ§€λ₯Ό 전달

Checked Exception

μ»΄νŒŒμΌλŸ¬κ°€ μ˜ˆμ™Έλ₯Ό 체크해주면, μž‘μ•„μ„œ μ²˜λ¦¬ν•˜κ±°λ‚˜, λ°–μœΌλ‘œ λ˜μ§€λ„λ‘ μ„ μ–Έ

μ˜ˆμ™Έλ₯Ό μž‘μ•„μ„œ μ²˜λ¦¬ν•  수 없을 κ²½μš°μ—λŠ” μ˜ˆμ™Έλ₯Ό throws둜 λ˜μ Έμ€˜μ•Ό 함.

  • μž₯점: μ‹€μˆ˜λ‘œ μ˜ˆμ™Έλ₯Ό λˆ„λ½ν•˜μ§€ μ•Šλ„λ‘ 컴파일러λ₯Ό 톡해 문제λ₯Ό μž‘μ•„μ£ΌλŠ” μ•ˆμ „ μž₯치

  • 단점: λͺ¨λ“  체크 μ˜ˆμ™Έλ₯Ό λ°˜λ“œμ‹œ μž‘κ±°λ‚˜ λ˜μ§€λ„λ‘ μ²˜λ¦¬ν•΄μ•Ό ν•˜λŠ” λ²ˆκ±°λ‘œμ›€

    • 크게 μ‹ κ²½μ“°κ³  싢지 μ•Šμ€ μ˜ˆμ™ΈκΉŒμ§€ λͺ¨λ‘ 챙겨야 ν•˜κ³ , μ˜μ‘΄κ΄€κ³„μ— λ”°λ₯Έ 단점도 쑴재

commit

ν™œμš©

\1. 기본적으둜 Unchecked(Runtime) Exceptionλ₯Ό μ‚¬μš©ν•˜μž.

  • Checked Exception은 Service, Controllerμ—μ„œ μ²˜λ¦¬ν•  수 μ—†λŠ” μ˜ˆμ™Έλ₯Ό throws μ„ μ–ΈμœΌλ‘œ 계속 λ˜μ§€λ‹€λ³΄λ©΄, 볡ꡬ λΆˆκ°€λŠ₯ν•œ μ˜ˆμ™Έ, 의쑴 관계 문제 λ°œμƒ

    • 볡ꡬ λΆˆκ°€λŠ₯ν•œ μ˜ˆμ™Έ: 둜그λ₯Ό 남기고 ServletFilter, SpringInterceptor, Spring ControllerAdviceλ₯Ό 톡해 μΌκ΄€μ„±μžˆκ²Œ κ³΅ν†΅μœΌλ‘œ μ²˜λ¦¬ν•˜μž. (μ‹€λ¬΄μ˜ λŒ€λΆ€λΆ„μ˜ μ˜ˆμ™Έλ“€μ€ 볡ꡬ λΆˆκ°€λŠ₯ν•œ μ‹œμŠ€ν…œ μ˜ˆμ™Έ)

    • 의쑴 관계 문제: μ²˜λ¦¬ν•  μˆ˜λ„ μ—†λŠ” SQLException에 μ˜μ‘΄ν•˜μ—¬ 기술이 λ³€κ²½λ˜λ©΄ 의쑴 μ½”λ“œλ₯Ό μ „λΆ€ 고쳐주어야 ν•˜λŠ” 문제 λ°œμƒ(OCP, DI μœ„λ°˜). -> Exception을 λ˜μ Έμ„œ 문제λ₯Ό ν•΄κ²°ν•  수 μžˆμ„ 것 κ°™μ§€λ§Œ, λͺ¨λ“  μ˜ˆμ™Έλ₯Ό λ‹€ 단지기 떄문에 체크 μ˜ˆμ™Έλ₯Ό 체크할 수 μžˆλŠ” κΈ°λŠ₯이 λ¬΄νš¨ν™”

\2. 체크 μ˜ˆμ™ΈλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μƒ μ˜λ„μ μœΌλ‘œ λ˜μ§€λŠ” μ˜ˆμ™Έλ₯Ό μž‘μ•„μ„œ λ°˜λ“œμ‹œ μ²˜λ¦¬ν•΄μ•Ό ν•˜λŠ” κ²½μš°μ—λ§Œ μ‚¬μš©ν•˜μž.

  • κ³„μ’Œ 이체 μ‹€νŒ¨ μ˜ˆμ™Έ

  • κ²°μ œμ‹œ 포인트 λΆ€μ‘± μ˜ˆμ™Έ

  • 둜그인 ID, PW 뢈일치 μ˜ˆμ™Έ

Unchecked Exception

μ»΄νŒŒμΌλŸ¬κ°€ μ²΄ν¬ν•˜μ§€ μ•ŠλŠ” μ˜ˆμ™Έ

체크 μ˜ˆμ™Έμ™€ 언체크 μ˜ˆμ™ΈλŠ” 기본적으둜 λ™μΌν•˜μ§€λ§Œ,

  • Checked Exception: μ˜ˆμ™Έλ₯Ό μž‘μ•„μ„œ μ²˜λ¦¬ν•˜μ§€ μ•ŠμœΌλ©΄ 항상 throws μ„ μ–Έ ν•„μš”

  • Unchecked Exception: μ˜ˆμ™Έλ₯Ό μž‘μ•„μ„œ μ²˜λ¦¬ν•˜μ§€ μ•Šμ•„λ„ throws μƒλž΅ κ°€λŠ₯

μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•  수 없을 λ•Œ μ˜ˆμ™Έλ₯Ό λ°–μœΌλ‘œ λ˜μ§€λŠ”λ°, throwsλ₯Ό ν•„μˆ˜λ‘œ μ„ μ–Έν•΄μ•Ό ν•˜λŠ”κ°€ μƒλž΅ν•  수 μžˆλŠ”κ°€μ˜ 차이가 큼

  • μž₯점: μ‹ κ²½μ“°κ³  싢지 μ•Šμ€ 언체크 μ˜ˆμ™ΈλŠ” λ¬΄μ‹œν•˜κ³  throws μ„ μ–Έ μƒλž΅ κ°€λŠ₯

  • 단점: μ»΄νŒŒμΌλŸ¬κ°€ μ˜ˆμ™Έ λˆ„λ½μ„ μž‘μ•„μ£Όμ§€ μ•ŠμœΌλ―€λ‘œ, μ‹€μˆ˜λ‘œ μ˜ˆμ™Έλ₯Ό λˆ„λ½ν•  수 있음

commit

ν™œμš©

  • CheckedException이 λ°œμƒν•˜λ©΄ RuntimeException으둜 μ „ν™˜ν•΄μ„œ μ˜ˆμ™Έλ₯Ό λ˜μ§€μž.

  • μ‹œμŠ€ν…œμ—μ„œ λ°œμƒν•œ μ˜ˆμ™ΈλŠ” λŒ€λΆ€λΆ„ 볡ꡬ λΆˆκ°€λŠ₯ μ˜ˆμ™Έμ΄λ―€λ‘œ, Runtime Exception을 μ‚¬μš©ν•˜λ©΄ μ„œλΉ„μŠ€λ‚˜ μ»¨νŠΈλ‘€λŸ¬κ°€ 볡ꡬ λΆˆκ°€λŠ₯ν•œ μ˜ˆμ™Έλ₯Ό 신경쓰지 μ•Šμ•„λ„ 되고 κ³΅ν†΅μœΌλ‘œ μ²˜λ¦¬ν•  수 μžˆλ‹€.

  • ν•΄λ‹Ή 객체가 μ²˜λ¦¬ν•  수 μ—†λŠ” μ˜ˆμ™ΈλŠ” λ¬΄μ‹œν•˜λ©΄ λ˜λ―€λ‘œ, μ˜ˆμ™Έλ₯Ό κ°•μ œλ‘œ μ˜μ‘΄ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.

  • RuntimeException은 놓칠 수 있기 λ•Œλ¬Έμ— λ¬Έμ„œν™”κ°€ μ€‘μš”

JPA EntityManager

/**
* Make an instance managed and persistent.
* @param entity entity instance
* @throws EntityExistsException if the entity already exists.
* @throws IllegalArgumentException if the instance is not an
* entity
* @throws TransactionRequiredException if there is no transaction when
* invoked on a container-managed entity manager of that is of type
* <code>PersistenceContextType.TRANSACTION</code>
*/
public void persist(Object entity);

JdbcTemplate

/**
* Issue a single SQL execute, typically a DDL statement.
* @param sql static SQL to execute
* @throws DataAccessException if there is any problem
*/
void execute(String sql) throws DataAccessException;

Stack Trace

μ˜ˆμ™Έλ₯Ό μ „ν™˜ν•  λ•ŒλŠ” λ°˜λ“œμ‹œ κΈ°μ‘΄ μ˜ˆμ™Έλ₯Ό 포함해야 ν•˜μž

  • 그렇지 μ•ŠμœΌλ©΄.. μŠ€νƒ 트레이슀λ₯Ό 확인할 λ•Œ μƒλ‹¨μ—μ„œ λ°œμƒν•œ μ˜ˆμ™Έλ₯Ό 확인할 수 μ—†λŠ” μ‹¬κ°ν•œ 문제 λ°œμ…

  • 둜그λ₯Ό 좜λ ₯ν•  λ•Œ λ§ˆμ§€λ§‰ νŒŒλΌλ―Έν„°μ— μ˜ˆμ™Έλ₯Ό λ„£μ–΄μ£Όλ©΄ λ‘œκ·Έμ— μŠ€νƒ 트레이슀 좜λ ₯ κ°€λŠ₯

@Test
void printEx() {
    Controller controller = new Controller();
    try {
        controller.request();
    } catch (Exception e) {
        log.info("ex", e);
    }
}

Spring Exception

  • μ„œλΉ„μŠ€ 계측은 가급적 νŠΉμ • κ΅¬ν˜„ κΈ°μˆ μ— μ˜μ‘΄ν•˜μ§€ μ•Šκ³ , μˆœμˆ˜ν•˜κ²Œ μœ μ§€ν•˜λŠ” 것이 μ’‹λ‹€.

  • μ˜ˆμ™Έμ— λŒ€ν•œ 의쑴(μ˜ˆμ™Έ λˆ„μˆ˜)을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λŸ°νƒ€μž„ μ˜ˆμ™Έμ™€ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ μš©ν•΄ 보자.

commit

μ ‘κ·Ό μ˜ˆμ™Έ 생성

Service Layerμ—μ„œ νŠΉμ • κΈ°μˆ μ— 의쑴적인 μ˜ˆμ™Έ(ex. SQLException)λ₯Ό μž‘μ•„μ„œ μ²˜λ¦¬ν•˜κ³  싢을 경우, RuntimeException μ˜ˆμ™Έλ₯Ό 속상받은 μ»€μŠ€ν…€ μ˜ˆμ™Έλ₯Ό Repository Layerμ—μ„œ λ³€ν™˜ν•΄μ„œ μ²˜λ¦¬ν•  수 있음

  • 단, SQL ErrorCodeλŠ” λ°μ΄ν„°λ² μ΄μŠ€ λ§ˆλ‹€ λ‹€λ₯΄λ―€λ‘œ λ°μ΄ν„°λ² μ΄μŠ€μ— 쒅속적

commit

μŠ€ν”„λ§μ˜ μ˜ˆμ™Έ 좔상화

μŠ€ν”„λ§μ€ 데이터 μ ‘κ·Ό 계측에 λŒ€ν•œ μΌκ΄€λœ μ˜ˆμ™Έ 좔상화λ₯Ό 제곡

  • μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” 데이터 μ ‘κ·Ό κ³„μΈ΅μ˜ λͺ¨λ“  μ˜ˆμ™ΈλŠ” λŸ°νƒ€μž„ μ˜ˆμ™Έ

  • DataAccessException

    • NonTransient Exception

      • μΌμ‹œμ μ΄μ§€ μ•Šμ€ μ˜ˆμ™Έ, 같은 SQL을 κ·ΈλŒ€λ‘œ 반볡 μ‹€ν–‰ν•˜λ©΄ μ‹€νŒ¨

      • ex. SQL 문법 였λ₯˜, λ°μ΄ν„°λ² μ΄μŠ€ μ œμ•½μ‘°κ±΄ μœ„λ°° λ“±

    • Transient Exception

      • μΌμ‹œμ μΈ μ˜ˆμ™Έ, ν•˜μœ„ μ˜ˆμ™ΈλŠ” λ™μΌν•œ SQL을 λ‹€μ‹œ μ‹œλ„ν–ˆμ„ λ•Œ 성곡할 κ°€λŠ₯μ„± 쑴재

      • ex. 쿼리 νƒ€μž„μ•„μ›ƒ, 락 κ΄€λ ¨ 였λ₯˜ λ“±

각 μ˜ˆμ™ΈλŠ” νŠΉμ • κΈ°μˆ μ— μ’…μ†λ˜μ§€ μ•Šκ²Œ 섀계

  • νŠΉμ • κΈ°μˆ μ„ μ‚¬μš©ν•˜λ©΄μ„œ λ°œμƒν•˜λŠ” μ˜ˆμ™Έλ₯Ό μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” μ˜ˆμ™Έλ‘œ λ³€ν™˜ν•˜λŠ” μ—­ν•  μˆ˜ν–‰

  • μ˜ˆμ™Έ λ³€ν™˜κΈ°λ₯Ό ν†΅ν•΄μ„œ SQLException의 ErrorCode에 λ§žλŠ” μ μ ˆν•œ μŠ€ν”„λ§ 데이터 μ ‘κ·Ό μ˜ˆμ™Έλ‘œ λ³€ν™˜

  • Service/Controller Layerμ—μ„œ μ˜ˆμ™Έ μ²˜λ¦¬κ°€ ν•„μš”ν•˜λ©΄ νŠΉμ • κΈ°μˆ μ— 쒅속적인 SQLException λŒ€μ‹  μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” 데이터 μ ‘κ·Ό μ˜ˆμ™Έλ₯Ό μ‚¬μš©

    • Repository

      public class MemberRepositoryImpl implements MemberRepository {
        private final DataSource dataSource;
        private final SQLExceptionTranslator exTranslator;
      
        public MemberRepositoryImpl(DataSource dataSource) {
            this.dataSource = dataSource;
            this.exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
        }
      
        //...
      
        @Override
        public Member save(Member member) {
            try {
                // save logic
            } catch (SQLException e) {
                throw exTranslator.translate("save", sql, e);
            } finally {
              // close
            }
        }
      }
    • Service

      try {
        MemberRepository.save(member);
      } catch (DuplicateKeyException e) {
        //..
      }

μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” SQL μ˜ˆμ™Έ λ³€ν™˜κΈ°

SQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("explanation", sql, e);
// => μ μ ˆν•œ μŠ€ν”„λ§ 데이터 μ ‘κ·Ό κ³„μΈ΅μ˜ μ˜ˆμ™Έλ‘œ λ³€ν™˜ν•΄μ„œ λ°˜ν™˜

assertThat(resultEx.getClass()).isEqualTo(BadSqlGrammarException.class);

SQL ErrorCode

  • SQL ErrorCodeλ₯Ό sql-error-codes.xml νŒŒμΌμ— λŒ€μž…ν•΄μ„œ μ–΄λ–€ 데이터 μ ‘κ·Ό μ˜ˆμ™Έλ‘œ μ „ν™˜ν•΄μ•Ό 할지 탐색

<bean id="H2" class="org.springframework.jdbc.support.SQLErrorCodes">
  <property name="badSqlGrammarCodes">
    <value>42000,42001,42101,42102,42111,42112,42121,42122,42132</value>
  </property>
  <property name="duplicateKeyCodes">
    <value>23001,23505</value>
  </property>
</bean>
<bean id="MySQL" class="org.springframework.jdbc.support.SQLErrorCodes">
  <property name="badSqlGrammarCodes">
    <value>1054,1064,1146</value>
  </property>
  <property name="duplicateKeyCodes">
    <value>1062</value>
  </property>
</bean>

commit

ν…œν”Œλ¦Ώ 콜백 νŒ¨ν„΄

  • JDBC의 반볡 문제λ₯Ό ν•΄κ²° JdbcTemplate

    • 컀λ„₯μ…˜ 쑰회, 컀λ„₯μ…˜ 동기화

    • PeparedStatement 생성 및 νŒŒλΌλ―Έν„° 바인딩

    • 쿼리 μ‹€ν–‰

    • κ²°κ³Ό 바인딩

    • μ˜ˆμ™Έ λ°œμƒμ‹œ μŠ€ν”„λ§ μ˜ˆμ™Έ λ³€ν™˜κΈ° μ‹€ν–‰

    • λ¦¬μ†ŒμŠ€ μ’…λ£Œ

  • νŠΈλžœμž­μ…˜μ„ μœ„ν•œ 컀λ„₯μ…˜ 동기화, μŠ€ν”„λ§ μ˜ˆμ™Έ λ³€ν™˜κΈ°λ„ μžλ™ μ‹€ν–‰

@Slf4j
public class MemberRepository implements MemberRepository {
    private final JdbcTemplate template;

    public MemberRepository(DataSource dataSource) {
        template = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        String sql = "insert into member(member_id, money) values(?, ?)";
        template.update(sql, member.getMemberId(), member.getMoney());
        return member;
    }

    @Override
    public Member findById(String memberId) {
        String sql = "select * from member where member_id = ?";
        return template.queryForObject(sql, memberRowMapper(), memberId);
    }

    @Override
    public void update(String memberId, int money) {
        String sql = "update member set money=? where member_id=?";
        template.update(sql, money, memberId);
    }

    @Override
    public void delete(String memberId) {
        String sql = "delete from member where member_id=?";
        template.update(sql, memberId);
    }

    private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setMemberId(rs.getString("member_id"));
            member.setMoney(rs.getInt("money"));
            return member;
        };
    }
}

Last updated