-
JDBCBackEnd/Spring DB 2023. 1. 25. 22:00반응형
Overview
해당 카테고리에 있는 내용은 인프런-스프링 DB 1편, 스프링 DB 2편 강의를 듣고 정리한 글입니다.
JDBC 등장 이유
클라이언트는 애플리케이션 서버를 통해 데이터베이스에 접근하며, 커넥션을 연결하고 SQL을 전달하여 결과를 응답받습니다. 문제는 MySQL, PostgreSQL, MariaDB, Microsoft SQL Server, Oracle Database 등 각각의 데이터베이스마다 커넥션을 연결하는 방법, SQL을 전달하는 방법, 그리고 결과를 응답 받는 방법이 모두 다르다는 점입니다. 데이터베이스 변경 시 개발자는 각각의 데이터베이스마다 커넥션 연결, SQL 전달, 그리고 결과를 응답 받는 방법을 새로 학습해야 하며, 애플리케이션 서버에 개발된 데이터베이스 사용 코드도 함께 변경해야 합니다. 이러한 문제를 해결하기 위해 JDBC라는 자바 표준이 등장하게 된 것입니다.
JDBC 표준 인터페이스
JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API다.
JDBC는 데이터베이스에서 자료를 쿼리하거나 업데이트하는 방법을 제공한다. - 위키백과
자바는 이렇게 표준 인터페이스를 정의하여 JDBC 드라이버를 통해 데이터베이스에 접근하게 하였습니다. JDBC 드라이버란 각각의 DB 벤더(회사)에서 자신의 DB에 맞도록 구현한 라이브러리 입니다(MySQL DB에 접근할 수 있는 것은 MySQL JDBC 드라이버, Oracle DB에 접근할 수 있는 것은 Oracle JDBC 드라이버). 데이터베이스 변경 시 JDBC 구현 라이브러리만 변경하면 됩니다. 개발자는 변경되는 데이터베이스의 커넥션 연결, SQL 전달, 그리고 결과를 응답 받는 방법을 새로 학습할 필요가 없으며, 애플리케이션 서버에 개발된 코드도 변경할 필요가 없습니다. 즉, JDBC 표준 인터페이스 사용법만 학습하면 수십개의 데이터베이스에 모두 적용할 수 있게 된 것입니다.
Note. 표준화의 한계
JDBC의 등장으로 많은 것이 편리해졌지만, 각각의 데이터베이스마다 SQL, 데이터타입 등의 일부 사용법이 다릅니다. ANSI SQL이라는 표준이 있기는 하지만 일반적인 부분만 공통화했기 때문에 결국 데이터베이스를 변경하면 JDBC코드는 변경하지 않아도 되지만, SQL은 해당 데이터베이스에 맞도록 변경해야 합니다.
JDBC와 최신 데이터 접근 기술
JDBC는 1997년에 출시될 정도로 오래된 기술이고, 사용하는 방법도 복잡합니다. 그래서 최근에는 JDBC를 편리하게 사용하는 다양한 기술이 존재합니다. 대표적으로는 SQL Mapper와 ORM 기술로 나눌 수 있습니다.
SQL Mapper
- 장점: SQL 응답 결과를 객체로 편리하게 변환해주고, JDBC의 반복 코드를 제거하여 JDBC를 편리하게 사용하도록 도와준다.
- 단점: 개발자가 SQL을 직접 작성해야한다.
- 대표기술: 스프링 JdbcTemplate, MyBatis
ORM(Object Relational Mapping)
ORM은 객체를 관계형 데이터베이스 테이블과 매핑해주는 기술입니다.
- 장점: SQL을 작성하지 않아도 되어 개발 생산성이 높아진다.
- 단점: 복잡한 쿼리를 처리하기 어렵고, 성능 저하의 위험이 있으며 학습 시간이 필요하다.
- 대표기술: JPA(자바 진영의 ORM 표준 인터페이스), 하이버네이트(JPA 구현 기술)
데이터베이스 연결
데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection(..)을 사용하면 됩니다. DriverManager는 라이브러리에 등록된 드라이버 목록을 자동으로 인식하여 드라이버들에게 순차적으로 커넥션을 획득할 수 있는지 확인합니다. 각각의 드라이버는 URL 정보를 체크해서 본인이 처리할 수 있는 요청인지 확인하고실제 데이터베이스에 연결해서 커넥션을 획득하고 이 커넥션을 클라이언트에게 반환합니다. 예제는 H2 데이터베이스를 사용합니다.
package hello.jdbc.connection; 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 = ""; }
package hello.jdbc.connection; import lombok.extern.slf4j.Slf4j; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import static hello.jdbc.connection.ConnectionConst.*; @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 IllegalStateException(e); } } }
JDBC 개발
아래 코드는 회원의 ID와 해당 회원이 소지한 금액을 데이터베이스에 CRUD하는 단순한 예제입니다. 전체 소스코드는 아래 참고 정보에 있습니다.
package hello.jdbc.repository; import hello.jdbc.connection.DBConnectionUtil; import hello.jdbc.domain.Member; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.NoSuchElementException; import lombok.extern.slf4j.Slf4j; /** * JDBC - DriverManager 사용 */ @Slf4j public class MemberRepositoryV0 { 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); 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 { 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); } } 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(); } }
주의. 리소스 정리는 꼭! 해주어야 합니다. 예외가 발생하든 하지 않든 항상 수행되어야 하기에 finally 구문에서 작성합니다. 커넥션이 끊어지지 않고 계속 유지되어 리소스 누수가 발생되면 커넥션 부족으로 장애가 발생할 수 있습니다.
[참고 정보]
반응형'BackEnd > Spring DB' 카테고리의 다른 글
JdbcTemplate (0) 2023.01.28 예외(Exception) (2) (0) 2023.01.28 예외(Exception) (1) (0) 2023.01.28 트랜잭션(Transaction) (0) 2023.01.27 커넥션 풀(Connection Pool) & 데이터 소스(DataSource) (0) 2023.01.26