[Spring Database] JDBC
Java DataBase Connectivity 의 약자로, 자바에서 데이터베이스에 접속할 수 있도록 도와주는 자바의 API이다.
클라이언트가 서버에게 데이터를 저장하거나 조회하려는 요청을 보내면 서버는 데이터베이스에 접근해 해당 요청을 실행한다.
잠시 과거의 데이터베이스 사용 방식에 대해 알아보자.
먼저 커넥션을 연결한다. (TCP / IP)
보통 JDBC드라이버와 데이터베이스간의 연결 시에는 TCP/IP 프로토콜을 사용한다.
그 후 서버는 데이터베이스가 이해할 수 있는 SQL을 연결된 커넥션을 통해 데이터베이스에 전달한다.
데이터베이스는 전달된 SQL을 실행하고, 결과를 서버에게 준다. 이후 서버는 응답 결과를 활용해 클라이언트의 요청을 수행한다.
각각의 데이터베이스마다 연결하는 방법과 SQL을 전달하는 방법, 결과를 받아오는 방법이 모두 다르다.
따라서 데이터베이스를 다른 종류의 데이터베이스로 변경하게 되면 서버에 개발된 데이터베이스 관련 코드도 모두 변경해야 하는 등 여러 문제가 발생한다.
이런 문제를 해결하기 위해 JDBC가 등장했다.
JDBC는 여러 기능을 표준 인터페이스로 정의해서 제공한다.
JDBC 인터페이스를 각각의 DB 회사에서 자사의 데이터베이스에 맞춰 구현해 라이브러리로 제공하는데,
이를 JDBC 드라이버라고 한다.
이렇게 드라이버만 바꿔서 사용할 수 있게 됐다.
이렇게 JDBC가 데이터베이스를 어느 정도는 표준화하는데 성공했지만..
아직도 각각의 데이터베이스마다 SQL, 데이터타입 등 일부 사용법이 다르다.
JDBC는 오래 전에 출시된 기술이고, 사용하는 방법도 복잡하다.
최근에는 JDBC를 직접 사용하기보다는 JDBC를 편하게 사용할 수 있도록 도와주는 SQL Mapper 와 ORM 을 사용하는 편이다. (Object Relational Mapping)
SQL Mapper는 SQL 응답 결과를 객체로 편하게 변환해주고, JDBC의 중복 코드를 제거해준다.
JdbcTemplate, MyBatis가 SQL Mapper 기술에 해당한다.
ORM 기술은 객체를 관계형 데이터베이스 테이블과 매핑해주는 기술이다.
즉, 애플리케이션 로직에서 객체를 JPA에게 넘겨주면 JPA가 알아서 SQL을 만들어 JDBC에게 전달한다.
덕분에 각각의 데이터베이스마다 다른 SQL을 작성해야 하는 문제도 어느정도 해결해준다.
ORM 기술은 JPA를 인터페이스로 두고 하이버네이트, 이클립스링크 등 여러 구현체를 사용한다.
SQL Mapper 기술은 개발자가 SQL만 직접 작성하면 나머지 귀찮은 작업들은 SQL Mapper가 대신 해 주기 때문에 SQL만 작성할 수 있으면 쉽게 배워서 사용할 수 있고,
ORM 기술은 SQL을 작성하지 않아도 되기에 잘 사용하면 개발 생산성이 매우 높아지지만, 충분한 학습이 필요하다.
두 기술의 공통점으로는 JDBC를 사용한다는 점이다.
개발자가 직접 JDBC를 조작할 일은 없겠지만, 해당 기술에 대한 깊은 이해와 문제가 발생했을 때 근본적인 해결책을 찾기 위해 JDBC가 어떻게 동작하는지에 관한 기본적인 원리는 알아두자.
@Slf4j
public class DBConnectionUtil {
public static Connection getConnection(){
try {
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
log.info("{} {}",connection, connection.getClass());
return connection;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
}
DriverManager는 라이브러리에 등록된 데이터베이스 드라이버들을 관리하고 커넥션을 획득하는 기능을 제공한다.
데이터베이스에 연결하려면 JDBC가 제공하는 DriverManager.getConnection() 메서드를 사용하면 된다.
라이브러리에 있는 드라이버를 찾고, 해당 드라이버가 제공하는 커넥션을 반환한다.
URL, 이름, 비밀번호 등 접속에 필요한 정보들을 바탕으로 드라이버들을 하나씩 순회하며 커넥션을 획득할 수 있는 드라이버를 찾는다.
JDBC로 회원 데이터를 관리하는 예제를 살펴보자.
@Slf4j
public class MemberRepositoryV0 {
// JDBC DriverManager
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values(?, ?)";
Connection con = null;
PreparedStatement pstmt = null;
con = getConnection();
try {
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
} catch (SQLException 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("err", 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 static Connection getConnection() {
return DBConnectionUtil.getConnection();
}
}
좀 지저분하다 -_-
preparedStatement(sql) 메서드로 데이터베이스에 전달할 SQL과 파라미터로 전달할 데이터들을 준비한다.
executeUpdate() 메서드로 준비된 SQL을 데이터베이스에 전달한다. (반환 값은 영향 받은 row 수)
쿼리를 실행한 후 리소스를 정리해야 한다.
예외가 발생하든 말든 리소스 정리는 수행돼야 하기 때문에 finally 문법을 사용한다. (정리는 역순으로 진행하자)
리소스를 정리하지 않아 커넥션이 끊어지지 않게 되면 리소스 누수가 발생하고, 커넥션 부족으로 장애가 발생할 수 있으니 주의하자.
PreparedStatement는 Statement의 자식으로 "?" 를 통한 파라미터 바인딩에 사용된다. (SQL Injection 방지)
회원 정보를 수정하는 경우와 삭제하는 경우도 등록과 거의 비슷하다. SQL 쿼리만 조금 수정해서 처리하자.
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);
}
}
회원을 조회하는 예제이다.
조회할 때는 executeQuery() 메서드를 사용한다. (저장할 때는 executeUpdate)
ResultSet에는 select 쿼리의 결과가 순서대로 들어간다.
오른쪽 표를 ResultSet 으로 생각하면 된다.
내부에 있는 커서를 이동시켜 다음 데이터를 조회한다.
rs.next() 메서드를 호출하면 커서가 다음 데이터로 이동한다. (최초의 커서는 아무것도 가리키지 않기 때문에 한 번은 호출해야 데이터 조회 가능)
rs.next() 메서드 실행 결과가 true이면 커서의 이동 결과 데이터가 있음을 의미하고, false이면 커서가 가리키는 데이터가 없음을 의미한다.
'Spring > Spring Database' 카테고리의 다른 글
[Spring Database] JdbcTemplate (0) | 2022.09.06 |
---|---|
[Spring Database] 데이터베이스 예외 처리 (0) | 2022.09.05 |
[Spring Database] Transaction AOP (1) | 2022.09.01 |
[Spring Database] Transaction / Lock (1) | 2022.09.01 |
[Spring Database] Connection Pool과 Data Source (0) | 2022.08.31 |
댓글
이 글 공유하기
다른 글
-
[Spring Database] 데이터베이스 예외 처리
[Spring Database] 데이터베이스 예외 처리
2022.09.05 -
[Spring Database] Transaction AOP
[Spring Database] Transaction AOP
2022.09.01 -
[Spring Database] Transaction / Lock
[Spring Database] Transaction / Lock
2022.09.01 -
[Spring Database] Connection Pool과 Data Source
[Spring Database] Connection Pool과 Data Source
2022.08.31