내가 JDBC를 공부하는 이유 -> 일단 이러한 LOW레벨의 동작원리를 알아야지 나중에 Template이나 JPA를 사용할 때 어쨋든 이 JDBC 기반으로 동작하기 때문에 이 근본이 되는 기술을 알아야한다고 생각했다.
일단 들어가기 앞서
전체적인 JDBC를 이용한 DB 연결 및 데이터 처리 흐름
- 애플리케이션
- "DB와 연결해서 데이터를 가져오거나 저장하고 싶다"고 요청
- DriverManager
- 등록된 여러 데이터베이스 드라이버 중에서 요청된 DB 유형(ex. MySql)에 맞는 드라이버를 선택
- DB 드라이버는 URL로 판단! (ex. Jdbc:mysql, Jdbc:h2 ..)
- DB 드라이버
- 선택된 드라이버가 DB와 TCP/IP로 연결을 시도
- DB에 ID, PW를 보내 인증 과정을 진행.
- 연결이 성공하면 Connection 객체를 만들어 애플리케이션에 반환
- Connection
- 데이터베이스와 연결된 상태를 유지하며, SQL을 전달하거나 결과를 받는 역할.
- PreparedStatement
- Connection을 통해 SQL문을 실행
- SQL에 전달할 값들을 안전하게 바인딩하고, 최적화된 방식으로 DB에 전달
- DB
- 전달받은 SQL을 실행하고, 그 결과를 반환
- 애플리케이션은 결과를 받아서 필요에 맞게 처리.
1. 먼저 SQL Table 생성
create table member
(
member_id varchar(10) primary key,
money integer not null default 0
);
2. DB와 연결
먼저 DB에 접속하는데 필요한 기본 정보를 작성
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 = "";
}
이제 JDBC를 사용해서 실제 DB와 연결하는 코드 작성
public class DBConnectionUtil {
public static Connection getConnection() {
try {
//DriveManager가 URL의 jdbc:h2로 시작하는 것을 보고 라이브러리에 등록된h2 DB에 접근을 시도한것.
//만약 h2가 아니라 Mysql이면 MySql에 접근을 시도
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
근데 DriverManager가 뭔데?
-> DriverManager는 DB 드라이버(MySql, H2..)를 통해 커넥션을 요청하고, DB 드라이버가 커넥션을 생성한 뒤 반환해주는 역할
쉽게 얘기해서 말 그대로 운전기사 관리자 같은 역할이다. 운전기사(DB 드라이버)에게 목적지(DB정보)를 알려주고, 운전기사(드라이버)가 실제 차(커넥션)를 만들어서 가져오는것.
- 애플리케이션이 DriverManager에게 커넥션을 요청
- DriverManager.getConnection(URL, USERNAME, PASSWORD); - DriverManager는 내부에 등록된 DB 드라이버들에게 "이 URL로 연결 좀 해줘!" 라고 요청
- DB 드라이버(실제 커넥션 생성)는
- DB와 연결(TCP/IP)을 맺고, ID/PW를 통해 인증한 후, DB 세션을 생성하고, 커넥션 객체를 반환한다.
그럼 또 궁금한게 있을 것이다. Connection이 뭐하는 애인데?
->
- DB와 연결 관리
- Connection 객체는 TCP/IP로 연결된 DB와의 통신을 관리한다.
- 예를 들어, 연결을 열고(open), 필요한 작업을 처리하고, 끝나면 닫아(close)해준다. - SQL 실행
- SQL 문장을 DB로 전달해 실행할 수 있다.
- Statement나 PrearedStatement를 사용해 SQL을 실행하고 결과를 받는데 활용된다. - 트랜잭션 관리
- 여러 SQL 작업을 하나의 묶음으로 처리하고, 작업 도중 문제가 발생하면 rollback을 할 수 있다.
- 예를 들어 connection.commit(), connection.rollback()
3. 등록
3.1 회원 클래스 생성
@Data
public class Member {
private String memberId;
private int money;
public Member() {}
public Member(String memberId, int money) {
this.memberId = memberId;
this.money = money;
}
}
3.2 멤버 생성
public class MemberRepositoryV0 {
public Member save(Member member) {
// 데이터베이스에 전달할 SQL을 정의.
String sql = "insert into member(member_id, money) values(?, ?)";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DBConnectionUtil.getConnection(); //데이터베이서 커넥션을 획득.
pstmt = con.prepareStatement(sql); //DB에 전달할 sql과 파라미터로 전달할 데이터들을 준비
pstmt = setString(1, member.getMemberId()); //sql의 첫번째 ?에 값을 지정
pstmt = setInt(2, member.getMoney());
pstmt = executeUpdate();
return member;
} catch (SQLException e) {
throw new RuntimeException(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);
}
}
}
}
- Connection con = null
- 데이터베이스랑 전화선을 만든다고 생각하면 된다.
- 데이터베이스에 말하려면 먼저 연결을 해야하니깐 con은 전화선 역할이다. 아직 연결 전이라 null로 시작하고, 나중에 연결하면 값이 들어가는 구조.
- 데이터베이스랑 전화선을 만든다고 생각하면 된다.
- con = DBConnectionUtil.getConnetcion()
- 이제 진짜로 데이터베이스랑 연결
- DBConnectionUtil.getConnection()은 전화선을 진짜로 연결해주는 작업.
- 연결 성공하면 con에 연결에 성공했다고 정보를 넣어준다.
- 이러면 DB와 대화할 준비가 완료!
- 이제 진짜로 데이터베이스랑 연결
- PreparedStatement pstmt = null;
- 데이터베이스에 보낼 문자를 쓰는 도구
- 데이터베이스한테 "뭐 좀 해줘"라고 말하려면 SQL이란 언어를 써야한다.
pstmt는 이 메시지를 준비하고 보낼 준비를 하는 역할을 한다.
- 데이터베이스한테 "뭐 좀 해줘"라고 말하려면 SQL이란 언어를 써야한다.
- 데이터베이스에 보낼 문자를 쓰는 도구
- pstmt = con.prepareStatement(sql);
- 데이터베이스한테 보낼 메시지(sql 쿼리)를 준비하는 과정
- 예를 들어"insert into member(member_id, money) values(?, ?)" 이걸 보낼거라고 알려준다.
?는 나중에 이 자리에 값을 넣을거라는 뜻. - 그러면 데이터 베이스는 준비하고 기다리고 있다.
- 예를 들어"insert into member(member_id, money) values(?, ?)" 이걸 보낼거라고 알려준다.
- 데이터베이스한테 보낼 메시지(sql 쿼리)를 준비하는 과정
- pstmt.executeUpdate() : Statement를 통해 준비된 SQL을 커넥션을 통해 실제 DB에 전달한다.
- executeUpdate()는 int를 반환하는데 영향 받은 DB row 수를 반환한다. 여기서는 하나의 row를 등록했으므로 1을 반환한다.
- PreparedStatement는 statement의 자식타입으로 더 많은 기능이 있다. 예를 들어 ?를 통한 매개변수 바인딩 기능을 제공한다.
- 사용을 하고 나면 리소스 정리는 꼭 해주어야한다!!
4. 멤버 조회
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 = DBConnectionUtil.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) {
throw e;
} finally {
close(con, pstmt, rs);
}
}
- findById : 쿼리 실행
- sql : 데이터 조회를 위한 select SQL를 준비
- rs = pstmt.executeQuery() 데이터를 변경할 때는 executeUpdate()를 사용하지만, 데이터를 조회할 때는 excuteQuery()를 사용한다. executeQuery()는 결과를 ResultSet에 담아서 반환한다.
- ResultSet : 쿼리 결과 저장, 주로 SELECT 쿼리의 결과를 처리하는데 사용
- 데이터베이스에서 조회한 결과를 테이블 형식으로 저장
- 예를 들어, select member_id, money라고 지정하면 member_id, money라는 이름으로 데이터가 저장
- 커서(Cursor)를 사용해 결과를 순차적으로 탐색.
- rs.next()를 통해 다음 데이터를 조회. 참고로 최초의 커서는 데이터를 가리키고 있지 않고 컬럼을 가리키고 있어서 rs.next()를 최초로 한번은 호출해야함.
- rs.getString("member_id") : 현재 커서가 가리키고 있는 위치의 member_id 데이터를 String 타입으로 반환
- rs.getInt("money") : 현재 커서가 가리키고 있는 위치의 money 데이터를 int 타입으로 반환
- 얘도 connection이나 statement처럼 리소스 누수를 방지하기 위해 꼭 자원 정리를 해야한다.
5. 수정 및 삭제
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 = DBConnectionUtil.getConnection();
pstmt = con.prepareStatement(sql);
pstmt = setInt(1, money);
pstmt = setString(2, memberId);
int resultSize = pstmt.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
close(con, pstmt, null);
}
}
public void delete(String memberId) throws SQLException {
String sql = "delete member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DBConnectionUtil.getConnection();
pstmt = con.prepareStatement(sql);
pstmt = setString(1, memberId);
pstmt.executeUpdate();
} catch (SQLException e) {
throw e;
} finally {
close(con, pstmt, null);
}
}
JDBC를 해보니 생각보다 단순한데 코드가 많이 지저분하고, 중복되는 코드들이 많다. 이제 기술이 어떻게 변화하는지 궁금해진다!
'Back-End > DataBase' 카테고리의 다른 글
| Spring JdbcTemplate의 RowMapper() (1) | 2024.12.09 |
|---|---|
| 커넥션 풀과 데이터소스 (0) | 2024.12.06 |
| SQL 기초 3 (Join) (1) | 2024.11.08 |
| SQL 기초 2 (2) | 2024.11.07 |
| SQL 기초 1 (3) | 2024.11.06 |