클라이언트가 애플리케이션 서버를 통해 데이터를 저장하거나 조회하면, 애플리케이션 서버는 데이터베이스를 사용한다.
- 커넥션 연결
- SQL 전달: 애플리케이션 서버는 DB가 이해할 수 있는 SQL을 연결된 커넥션으로 전달
- 결과 응답: SQL을 수행하고 결과 응답.
JDBC를 직접 코딩할 일은 웬만하면 없다. Sql Mapper나 ORM 기술을 사용하기 때문에
하지만 JDBC를 이해할 필요는 있다.
@Slf4j
public class DBConnectionUtil {
public static Connection getConnection() {
try {
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
//라이브러리에 있는 데이터베이스 드라이버를 찾아서 해당 드라이버가 제공해주는 커넥션 반환
log.info("get connection = {}, class = {}", connection, connection.getClass()); //객체 정보와 타입 정보
return connection;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
}
/**
* JDBC - Driver Manager 사용
*/
@Slf4j
public class MemberRepositoryV0 {
Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values (?, ?)";
Connection con = null;
PreparedStatement pstmt = null; //PreparedStatement는 파라미터 할당 기능이 추가된 것.
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate(); //데이터를 변경할 때. 위에서 설정한게 실행됨. 영향받은 row 수만큼 반환
return member;
} catch (SQLException e) {
log.error("DB error", e);
throw e;
} finally {
close(con, pstmt, null); //finally에서 close해야 항상 close
}
}
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(); //select할 때는 executeQuery. 결과를 ResultSet에 담아서 반환
if(rs.next()) { //내부에 커서가 있는데 한번은 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);
}
}
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 = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, money);
pstmt.setString(2, memberId);
int resultSize = pstmt.executeUpdate();
log.info("resultSize = {}", resultSize);
} catch (SQLException e) {
log.error("DB error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
public void delete(String memberId) throws SQLException {
String sql = "delete from member where member_id = ?";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, memberId);
pstmt.executeUpdate();
} catch (SQLException e) {
log.error("DB error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
private void close(Connection con, Statement stmt, ResultSet rs) {
//ResultSet은 결과를 조회할 때(select문)에서 사용
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);
}
}
//시작과 역순으로 닫아주기
//이렇게 각각을 try/catch로 해야 서로에게 영향을 주지 않음.
//하나에서 exception이 터져도 다른 하나가 close가 안되는 일이 생기지 않음.
}
private static Connection getConnection() {
return DBConnectionUtil.getConnection();
}
}
Test
@Slf4j
class MemberRepositoryV0Test {
MemberRepositoryV0 repository = new MemberRepositoryV0();
@Test
void crud() throws SQLException {
//save
Member member = new Member("memberV40", 10000);
repository.save(member);
//findById
Member findMember = repository.findById(member.getMemberId());
log.info("findMember = {}", findMember);
log.info("member == findMember = {}", member == findMember); //동일성(identity) 두 객체의 주소값이 같은지
log.info("member equals findMember = {}", member.equals(findMember)); //동등성(equality) 두 객체의 내용이 같은지
assertThat(findMember).isEqualTo(member);
//update: money: 10000 -> 20000
repository.update(member.getMemberId(),20000);
Member updatedMember = repository.findById(member.getMemberId());
assertThat(updatedMember.getMoney()).isEqualTo(20000);
//delete
repository.delete(member.getMemberId());
Assertions.assertThatThrownBy(()-> repository.findById(member.getMemberId()))
.isInstanceOf(NoSuchElementException.class);
}
}
마지막에 회원을 삭제하므로 memberId를 바꾸지 않아도 같은 테스트를 반복해서 수행할 수 있다.
하지만 테스트 중간에 오류가 발생하면 삭제가 되지 않아 반복해서 수행할 수 없게 된다.
이 문제는 트랜잭션을 활용하면 해결할 수 있다.
728x90
'Spring' 카테고리의 다른 글
트랜잭션 (3) | 2024.02.08 |
---|---|
커넥션 풀 (0) | 2024.02.01 |
Spring Toy Project-1 (3) | 2024.01.31 |
스프링 타입 컨버터 (1) | 2024.01.15 |
API 예외 처리 (0) | 2024.01.13 |