애플리케이션을 개발할 때 다음과 같이 데이터베이스와 연결한다.
그런데, 과거에는 DB마다 커넥션 연결, SQL 전송 방법 등이 다 달라서 만약 DB를 변경하면 관련 코드들을 싹 바꿔야하는 번거로움이 있었다.
그래서 JDBC라는 자바 표준이 나왔다.
JDBC는 과거 기술이고 JDBC를 직접 사용하기에는 복잡하기 때문에 이러한 JDBC를 편리하게 사용할 수 있는 기술들이 등장했다
먼저 ORM이란 Object 객체를 관계형 DB 테이블과 매핑해주는 기술이다.
위에서 되게 자주 보이는 단어가 있다.
JDBC다. 즉 근본 기술인 JDBC의 동작 원리를 알고이었어야 한다.
이제 코드로 JDBC에 대해 알아보자.
우선 여기선 학습용으로 좋은 H2 데이터베이스를 사용한다.
우선 H2 데이터베이스에 접속하기 위한 기본 정보들인 URL과 USERNAME, PASSWORD들은 자주 사용할테니 상수로 정의한다.
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 = "";
}
public class DBConnectionUtil {
public static Connection getConnection() {
try {
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
return connection;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
}
DriverManager는 라이브러리에 등록된 DB 드라이버들의 목록을 자동으로 인식하고, 이러한 드라이버들을 관리하고 커넥션을 획득하는 기능을 제공해준다.
어떻게 찾았는지는 다음과 같은 프로세스를 거친다.
커넥션 획득을 위해 DriverManager.getConnection(...) 호출
라이브러리에 등록된 드라이버들에게 파라미터로 넘겨온 정보들을 넘긴다.
각각의 드라이버들이 URL을 보고 자신이 처리할 수 있는 데이터베이스인지 아닌지를 확인한다.
3번 과정을 거치면서 처리할 수 있는 드라이버를 찾으면 해당 드라이버가 실제 DB와 연결해서 Connection을 획득하고 반환한다.
다음과 같이 Member 테이블이 있다고 하자.
Member 테이블에 데이터를 추가하는 코드를 보자.
public Member save(Member member) throws SQLException{
String sql = "insert into member(member_id, money) values(?,?)";
Connection con = null;
// Preparestatement를 가지고 DB에 쿼리를 날리는 것
PreparedStatement pstmt = null;
try {
// Connection 가져오는 부분
con = DBConnectionUtil.getConnection();
// DB에 날릴 SQL을 등록
pstmt = con.prepareStatement(sql);
// (?, ?) 파라미터 바인딩
pstmt.setString(1, member.getMemberId()); // 1번 파라미터 - member_id
pstmt.setInt(2, member.getMoney()); // 2번 파라미터 - money
pstmt.executeUpdate(); // 쿼리를 실제 DB에 날리는 것 = 실행
return member;
}catch(SQLException e) {
log.info("db error", e);
throw e;
}finally {
// 시작과 역순으로 close 해주기
// 근데 pstmt에서 Exception이 발생하면 con.close()가 실행되지 않을 수 있어서 다음과 같은 처리를 해주어야함.
close(con, pstmt, null);
}
}
private void close(Connection con, Statement stmt, ResultSet rs){
// Statement는 쿼리를 바로 넣는 것이고
// preparestatement는 파라미터를 바인딩할 수 있는 것
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 (stmt != null) {
try {
con.close();
} catch (SQLException e) {
log.info("error",e);
}
}
}
id를 가지고 Member를 조회하는 코드이다.
save()와는 쿼리문은 다르겠지만 되게 비슷한 부분의 설명은 넘어가겠다.
그리고 조회하는 쿼리문의 실행은 pstmt.executeQuery()이며 반환값으로는 조회 결과를 ResultSet 타입으로 받는다.
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(); // 조회하는 쿼리를 날릴때 사용하는 메서드이며 반환값은 ResultSet
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);
}
}
속성이 2개밖에 없는 테이블에 데이터를 insert와 select하는 간단한 쿼리문만 작성했는데 코드의 양은 어마무시하다. 수정, 삭제도 비슷하게 구현이 될텐데 코드 양이 어마무시할 것이다.
그래서 ORM이나 SQL Mapper기술이 나왓다 싶다...............
본 포스팅은 스프링 DB 1편를 공부하면서 정리한 글입니다.