이 글에서 설명하는 방법은 현재 직접적으로 사용하는 방법은 아니다. 아래의 과정을 편리하게 라이브러리화 하여 사용한다. 그러나 JAVA의 모든 데이터 접근 기술은 이런 복잡한 과정을 자동화하여 개발자가 이용하기 쉽게 만든 것뿐, 내부에서는 똑같이 동작한다.
JDBC 표준 인터페이스
JDBC (Java Database Connectivity ) : 자바에서 데이터베이스에 접근할 수 있도록 하는 자바 API 이다.
JDBC는 연결, SQL 전달, 결과 응답의 3가지 기능을 표준 인터페이스로 정의해서 제공한다.
- Connection : 연결
- Statement : SQL 전달
- ResultSet : 결과 응답
인터페이스로 제공하는 이유는 여러 가지 DB (MySQL, Oracle 등 ) 마다 조금씩 사용법이 다르기 때문이다.
따라서 JDBC 코드는 변경하지 않더라도, 각 DB마다의 SQL 문법에 맞게 sql 문자열을 변경해야 한다.
데이터베이스 연결 - DriverManager
DriverManager을 통해 데이터베이스에 연결하기 전에, 연결할 DB의 URL, username, password를 알아두자.
DriverManager.getConnection()을 사용하면, 라이브러리에서 데이터베이스 드라이버를 찾아서 해당 드라이버가 제공하는 커넥션을 반환해 준다.
아래처럼 따로 메서드로 빼서 사용하는 것이 좋겠다.
public static Connection getConnection(){
try {
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
return connection;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
실행 전에 데이터베이스 서버를 실행시켜두어야 한다.
- 애플리케이션 로직에서 커넥션이 필요하면 DriverManager.getConnection()을 호출한다.
- DriverManager은 라이브러리에 등록된 드라이버 목록을 자동으로 인식해서 (URL, USERNAME, PASSWORD)를 확인해서 불러올 수 있는 커넥션을 반환한다. ( 만약 라이브러리에 H2, MySQL 드라이버가 둘 다 존재하더라도 URL, USERNAME, PASSWORD 정보에 맞게 불러올 수 있는 드라이버는 하나밖에 없을 것이다. )
- DriverManager은 위의 방법으로 커넥션을 찾아서 애플리케이션에 반환한다.
위에서 사용된 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 = "";
}
Connection, Statement
데이터베이스에서 데이터를 불러올 때 Connection, Statement, ResultSet 순으로 호출한다.
1. SQL
String으로 sql 문자열을 만들고, ' ? ' 자리에 값들을 대입하여 SQL문을 완성한다.
String sql = "insert into member(member_id, money) values (?, ?)";
2. Connection, Statement
getConnection()으로 커넥션을 획득한다.
con.prepareStatement()로 데이터베이스에 전달할 SQL과 데이터를 준비한다.
Connection con = null; //커넥션
PreparedStatement pstmt = null; // db에 쿼리를 날리는 도구
con = getConnection(); // 커넥션 획득 메소드
pstmt = con.prepareStatement(sql);
3. pstmt에 데이터 삽입
첫 번째 파라미터는 몇 번째 '? '에 값을 넣을 것인지 지정하고, 두 번째 파라미터로 값을 지정한다.
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
4. executeUpdate()
Statement를 통해 준비된 SQL을 실제로 데이터베이스에 전달한다.
해당 메서드는 int를 반환하는데, 해당 쿼리로 영향받은 DB의 tuple 수를 반환한다.
pstmt.executeUpdate();
return member;
5. 리소스 정리
쿼리를 실행하고 나면 리소스를 꼭 정리해야 한다.
con - pstmt - (resultset) 순으로 만들었기 때문에 그 역순으로 (resultset) - pstmt - con 순으로 리소스를 반환한다.
pstmt.close();
con.close();
예외처리
중간에 예외가 발생하면, 일정 부분만 실행되고, 그 뒤는 실행되지 않을 수 있다. 그렇게 되면 리소스를 반환하지 않게 될 수도 있다. 리소스정리는 예외 유무에 상관없이 항상 수행해야 하기 때문에 항상 try catch로 묶고, finally 구문을 이용해 작성해야 한다. 또, 리소스를 반환하는 과정에서도 오류 발생 시 다음 리소스 반환이 되지 않을 수 있기 때문에 모두 예외처리를 해줘야 한다.
try {
con = getConnection(); // 커넥션 획득 메소드
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId()); //parameter binding ( 위에서 ?,? ) 부분
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
} 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);
}
}
}
ResultSet
ResultSet은 select 구문과 같이 DB로부터 데이터를 조회할 때 사용한다.
- ResultSet을 추가로 선언해 준다.
- 데이터를 변경할 때는 executeUpdate()를 사용하지만, 조회할 때는 executeQuery()를 사용한다.
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);
}
}
ResultSet 구조
ResultSet은 우리가 흔히 아는 DB 테이블과 비슷한 구조를 가지고 있다.
ResultSet 내부의 커서를 이동해서 다음 데이터를 조회한다. 따라서 rs.next()를 이용해서 한 줄씩 읽어온다.
최초의 커서는 데이터를 가리키고 있지 않기 때문에 한 번은 rs.next()를 이용해서 첫 번째 데이터를 가리키도록 해줘야 한다. rs.next()는 다음 데이터가 존재할 시 true를, 존재하지 않을 시 false를 반환한다.
위의 예제에서는 하나의 데이터만을 호출하는 예제이기 때문에 if(rs.next())를 사용했지만, 여러 개의 데이터를 조회하는 경우에는 while(rs.next())를 사용해야 할 것이다.
(참고) 인프런 - 김영한 님 스프링 DB
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard
'Database > 데이터 접근 기술' 카테고리의 다른 글
[SQL/Mysql, MariaDB] 드라이버 연결 (0) | 2023.07.25 |
---|---|
[MyBatis] 동적쿼리, 기타 문법 (0) | 2023.05.15 |
[MyBatis] 마이바티스 기본 사용법 (2) | 2023.05.15 |
[Jdbc] JdbcTemplate 사용법 및 적용예제 (0) | 2023.05.04 |
[JDBC] 커넥션 풀, DataSource (0) | 2023.05.01 |