JDBC
등장배경
과거에는 각각의 데이터베이스마다 커넥션을 연결하는 방법, SQL을 전달하는 방법, 결과를 받는 방법이 달랐다.
그래서 다른 종류의 데이터베이스로 변경하면 애플리케이션 서버에 개발된 데이터베이스 사용 코드도 함께 변경해야 했다. 또한, 개발자가 각각의 데이터베이스의 사용법을 추가로 학습해야 했다.
이러한 문제를 해결하기 위해 자바에서 데이터베이스에 접속할 수 있도록 하는 표준 인터페이스
가 등장했는데 이것이 JDBC
이다.
그리고 이 JDBC 인터페이스를 가지고 각각의 데이터베이스 회사에서 구현한 라이브러리가 JDBC 드라이버
이다.
하지만 JDBC 가 모든 것을 표준화하지는 못했다. 예를 들어 페이징 쿼리 문법의 경우 데이터베이스마다 다르기 때문에 다른 종류의 데이터베이스로 변경하면 SQL 을 수정해야 한다.
JPA(Java Persistance API) 를 사용하면 각각의 데이터베이스마다 SQL 문법이 다른 문제를 해결할 수 있다.
JDBC 를 편리하게 사용할 수 있도록 도와주는 기술
- SQL Mapper : JdbcTemplate , MyBatis
- ORM : JPA , 하이버네이트
순수 JDBC 구현 예제
INSERT / UPDATE / DELETE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 1. DriverManager 를 통해 Connection 객체를 얻는다.
* 2. SQL 문을 이용해 PreparedStatement 객체를 얻는다.
* 3. PreparedStatement 객체를 이용해 SQL의 ? 에 바인딩할 값을 설정한다.
* 4. executeUpdate() 메서드를 호출해 쿼리를 실행한다.
*/
public void cud(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?, ?)";
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
if(pstmt != null) {
pstmt.close();
}
if(conn != null) {
conn.close();
}
}
- DriverManager 는 프로젝트에 의존성이 추가된 DB 드라이버를 관리하는 클래스이다. getConnection(…) 메서드로 넘어온 값을 이용해 DB 드라이버를 순회하며 커넥션을 요청하고 정상적으로 연결이되면 커넥션을 획득한 구현체를 반환한다.
- executeUpdate() 메서드는 SQL이 처리한 row 의 개수를 반환한다.
- SQL Injection 공격을 예방하려면 PreparedStatement 를 이용한 파라미터 바인딩 방식을 사용해야 한다.
SELECT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public Member findById(String memberId) throws SQLException {
String sql = "select * from member where member_id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
pstmt = conn.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) {
throw e;
} finally {
if(rs != null) {
rs.close();
}
if(pstmt != null) {
pstmt.close();
}
if(conn != null) {
conn.close();
}
}
}
executeQuery() 메서드는 ResultSet 객체를 반환한다.
ResultSet 내부에 있는 커서를 이동시켜 데이터를 추출할 수 있다.
최초의 커서는 데이터를 가지고 있지 않으므로 최소 한번은 rs.next() 메서드를 호출해서 커서를 옮겨야 한다.
rs.next() 메서드를 호출했을 때 데이터가 있으면 true 없으면 false 를 반환한다.
쿼리를 실행하고 모든 작업이 끝나면 항상 리소스를 정리해야 한다. 그렇지 않으면 데이터베이스 커넥션 부족으로 장애가 발생할 수 있다.
이 때, Connection → PreparedStatement → ResultSet 순서로 객체를 초기화 했다면 리소스 정리는 역순으로 해줘야한다.