개요
JDBC의 역사를 알아보겠습니다.
일반적인 애플리케이션 - DB 사용법
아래 그림처럼, 애플리케이션 서버는 총 3가지 과정으로 DB와 연동하여 원하는 데이터를 조회, 추가, 수정, 삭제 등을 합니다. 첫째로 TCP/IP 커넥션 연결, 두번째로 SQL 전달, 세번째로 결과 응답입니다.
- 문제점
1. DB 종류에 따라 데이터베이스 사용 코드가 달라집니다.(MySQL, Oracle 등등)
2. 개발자는 직접 커넥션 연결, SQL 전달, 결과 응답 코드 작성을 데이터베이스마다 학습해야 합니다.
JDBC 표준 인터페이스
JDBC는 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API입니다. JDBC는 데이터베이스에서 자료를 쿼리 하거나 업데이트하는 방법을 제공합니다 - 나무위키
JDBC 표준 인터페이스 덕분에 각각 데이터베이스마다 코드 사용법을 익힐 필요가 없어졌습니다. 수동으로 했던 연결은 Connection, SQL 전달은 Statement, 결과 응답은 ResultSet으로 만들었습니다.
각 회사는 JDBC 인터페이스를 구현하여 라이브러리로 제공하는데, 이를 JDBC 드라이버라고 합니다. MySQL은 MySQL 드라이버, Oracle은 Oracle 드라이버를 사용합니다.
아래 그림처럼, JDBC 인터페이스 구현체가 바뀌더라도 코드 전체를 바꾸는 것이 아니라 JDBC 라이브러리(드라이버)만 변경하면 됩니다. 또한 연결, SQL 전달, 결과 응답이 인터페이스로 정의되어 있기 때문에 모든 데이터베이스에 동일하게 적용되므로 학습량이 줄었습니다.
드라이버 사용으로 편해졌지만 여전히 문제점이 있습니다.
- 문제점
1. 대표적으로 페이징 등의 여전히 DB 의존적인 쿼리문들이 있습니다.
2. JDBC 인터페이스 코드들이 반복이 많아 지저분합니다.
SQL Mapper와 ORM
JDBC 드라이버를 통해서 코드 작성이 간편해졌지만, 추가적으로 2가지 기술이 더 등장합니다.
1. SQL Mapper
- 장점
1. SQL 응답 결과를 객체로 반환해줍니다.
2. JDBC의 반복적인 코드를 줄여줍니다.
- 단점
1. 개발자가 SQL을 직접 작성해야 합니다.
대표 기술 : JdbcTemplate, MyBatis
2. ORM
- 장점
1. SQL문을 작성하지 않아도 자동으로 동적 생성을 합니다.
2. DB마다 다르게 사용하는 SQL도 JPA에서 동일하게 작성할 수 있습니다.
- 단점
1. 러닝 커브가 있습니다.
2. SQL 작성이 없지만, SQL 작성은 개발자라면 알아야 합니다.
대표 기술 : 하이버네이트, 이클립스링크
DriverManger의 역할
DriverManager는 개발자가 설정한 데이터베이스 정보를 활용해 해당 데이터베이스의 Connection을 연결합니다.
h2 데이터베이스를 이용하여 Connection을 연결해보겠습니다. static 상수로 DB정보를 정의합니다. DriverManager.getConnection()을 하면, DriverManager는 자동으로 h2의 커넥션을 찾아서 연결합니다. 아래는 DriverManager에 h2 정보를 연결하는 코드입니다.
아래의 Connection은 java.sql.Connection의 Connection으로, DriverManager.getConnection(URL, USERNAME, PASSWORD)를 통해 h2의 드라이브 정보를 찾아서 Connection 연결을 합니다.
아래는 h2 데이터베이스 라이브러리 내부에 있는 JdbcConnection 클래스로 Connection을 구현하도록 정의되어 있습니다. DriverManager는 해당 클래스 정보를 찾아 h2의 Connection을 만듭니다.
즉, DriverManger.getConnection()을 사용하면, 아래 사진처럼 라이브러리에 등록된 드라이브 중에 내부의 설정된 정보에 따라서 h2 드라이버를 연결할 수도, MySQL 드라이버를 연결할 수도 있습니다.
JDBC CRUD 코드 예시
아래의 close()와 getConnection()은 공통으로 계속 사용하는 함수입니다. Connection, Statement, ResultSet의 사용을 모두 종료해야 합니다. 제대로 종료되지 않으면 불필요하게 Connection을 점유하고 있을 수 있기 때문에 null 검사를 하고 정상적으로 종료해야 합니다.
Close
private void close(Connection con, Statement stmt, ResultSet rs) {
if(rs != null) {
try {
rs.close(); //Exception
} catch (SQLException e) {
log.info("rs error", e);
}
}
if(stmt != null) {
try {
stmt.close(); //Exception
} catch (SQLException e) {
log.info("stmt error", e);
}
}
if(con != null) {
try {
con.close();
} catch (SQLException e) {
log.info("con error", e);
}
}
}
private Connection getConnection() {
return DBConnectionUtil.getConnection();
}
save
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());
int count = pstmt.executeUpdate();
return member;
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
findById
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);
}
}
update
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);
}
}
delete
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);
}
}
전체적으로 연결하고 해제하는 코드가 지저분하게 중복되어 사용되고 있으며, 개발자가 온전히 비지니스에 집중하기 힘든 구조입니다. 따라서 Jdbc 사용은 데이터베이스 종류별로 작성해야 하는 코드의 양을 줄여주지만, 마찬가지로 반복적인 코드를 사용해야 하는 불편함이 있습니다. 이후에 좀 더 효율적인 DB 사용방법을 알아보겠습니다.
*참고
스프링 DB 1편 - 데이터 접근 핵심 원리 강의
'학습 > DB' 카테고리의 다른 글
인덱스 스캔 7가지 종류 (0) | 2022.10.08 |
---|---|
Connection Pool & DataSource (0) | 2022.09.02 |
DB의 인덱스와 B-tree, B+tree (0) | 2022.08.24 |
낙관적 잠금과 비관적 잠금으로 동시성 해결하기 (0) | 2022.08.19 |
트랜잭션 격리 수준 (0) | 2022.08.12 |