[코드리뷰 정리] Level 1. 체스 미션 - JDBC
2021-04-10글
JDBC (Java DataBase Connectivity)
JDBC는 DB에 접근할 수 있도록 Java에서 제공하는 API로, 모든 Java의 Data Access 기술의 근간이다.
모든 Persistence Framework는 내부적으로 JDBC API를 이용한다.
JDBC를 통한 DB 연결
1. 드라이버 로드
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.err.println(" !! JDBC Driver load 오류: " + e.getMessage());
e.printStackTrace();
}
제공되는 클래스를 인스턴스화해서 내부적으로 저장해 메모리에 드라이버를 사용할 수 있게한다.
이 메소드를 통해 드라이버를 사용할 수 있도록 초기화 한다.
생성자를 통한 인스턴스 생성과 같다고 볼 수 있다.
2. Connection 객체 생성
DriverManager 클래스는 드라이버를 통해 Conection 객체를 만든다.
Connection은 DB와 연결하는 객체로 DB와 연결하는 통로이며, 이를 통해 쿼리를 전달하고 결과값을 반환받는다.
try {
con = DriverManager.getConnection("jdbc:mysql://" + server + "/" + database + option, userName, password);
System.out.println("정상적으로 연결되었습니다.");
} catch (SQLException e) {
System.err.println("연결 오류:" + e.getMessage());
e.printStackTrace();
}
3. Statement / PreparedStatement 객체 생성
Statement 객체를 통해 insert 쿼리를 작성하면 아래와 같이 각각의 값을 콤마와 따옴표로 구별해야하기 때문에
가독성도 떨어지고 잘못 입력할 확률도 커진다.
statement = connection.createStatement();
String query = "INSERT INTO piece(color, name, position, chessGameId) VALUE ('" + color + "','" + name + "','" + position + "','" + chessGameId "')";
resultCount = statement.executeUpdate(insertQuery);
PreparedStatement 객체를 사용하면 다음과 같이 속성 값을 ?로 설정하고 set을 통해 설정하면, 자동으로 쿼리를 완성시켜준다.
String query = "INSERT INTO piece(color, name, position, chessGameId) VALUE (?, ?, ?, ?)";
try (Connection connection = dbManager.getConnection();
PreparedStatement pstmt = connection.prepareStatement(query)) {
for (Piece piece : pieces) {
pstmt.setString(1, piece.color().name());
pstmt.setString(2, piece.name());
pstmt.setString(3, piece.position().key());
pstmt.setInt(4, chessGameId);
pstmt.executeUpdate();
}
} catch (SQLException e) {
e.printStackTrace();
}
executeQuery()
: select를 통한 정보를 조회하고, 쿼리를 전송 후 결과 객체 반환한다.executeUpdate(String query)
: Insert, Update, Delete 쿼리를 전송하고 변경된 레코드 수를 반환한다.
4. ResultSet 객체 반환
ResultSet 객체는 select 쿼리의 결과값을 모두 가지고 있는 객체이다.
while (rs.next()) {
Piece piece = PieceFactory.findByInfo(rs.getString("color"),
rs.getString("name"), rs.getString("position"));
pieces.add(piece);
}
boolean next()
: 결과 레코드가 존재하면 true, 없으면 falseboolean previous()
: 이전 레코드로 이동 (가장 첫 행이면 false)boolean first()
: 처음 위치로 이동 (레코드가 없으면 false)boolean last()
: 마지막 위치로 이동 (레코드가 없으면 false)String getString(String colLabel)
: 현재 커서 위치의 컬럼명에 해당하는 문자열 반환int getInt(String colLabel)
: 현재 커서 위치의 컬럼명에 해당하는 정수값 반환String getString(int colIndex)
: 컬럼 인덱스에 해당하는 문자열 반환 (1부터 시작)int getInt(int colIndex)
: 컬럼 인텍스에 해당하는 정수값 반환 (1부터 시작)
5. 자원 해제
DB 관련 작업을 하면서 Connection, Statement / PreparedStatement, ResultSet 객체를 사용했다.
DB 관련 처리 작업이 완료되었다면, 사용했던 객체들을 메모리에서 해제해주어야 한다.
만약 해당 자원들을 해제해주지 않으면 다음과 같은 문제점이 발생할 수 있다.
- Connection pool을 사용하지 않은 상태에서 Connection을 닫지 않으면 DBMS에 연결된 새로운 Connection을 생성할 수 없다.
- Statement / PreparedStatement를 해제하지 않으면, 생성된 갯수가 증가하면서 더 이상 새로운 Statement / PreparedStatement를 생성할 수 없다.
이번 미션에서는 try-with-resource를 통해 자원 close 처리 개선을 하였다.
이는 try 블록의 소괄호 안에서 자원 해제가 필요한 객체를 할당하면 try catch 절이 종료되면서
자원 해제가 필요한 connection과 preparedstatement를 자동으로 close되는 try-catch 문이다.
preparedstatement를 해제하면 현재(가장 최근에 생성한) ResultSet도 자동으로 해제된다.
public List<Piece> findAllByChessGameId(int chessGameId) {
List<Piece> pieces = new ArrayList<>();
String query = "SELECT color, name, position FROM piece WHERE chessGameId = ?";
try (Connection connection = dbManager.getConnection();
PreparedStatement pstmt = connection.prepareStatement(query)) {
pstmt.setInt(1, chessGameId);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Piece piece = PieceFactory.findByInfo(rs.getString("color"),
rs.getString("name"), rs.getString("position"));
pieces.add(piece);
}
} catch (SQLException e) {
e.printStackTrace();
}
return pieces;
}
SQL Exception에 대하여
checked exception인 SQLException에 대해 어떻게 처리를 할지 고민하다가 결국 외부로 던져주기만 했었다.
휴의 피드백대로 지금은 DAO에서 stacktrace만 출력하도록 변경하였다.
추후에 휴가 SQLException에 대한 참고 자료를 보내주셨다.
[Toby spring] 사라진 SQLException에 따르면 DAO에서 발생하는 예외를 계속해서 외부로 던져주는 것도 하나의 방법이지만,
이렇게 되면 예외가 발생한 DAO를 사용한 Service에서 왜 SQLException이 발생했는지 알 수 없기 때문에
아래와 같이 SQLException을 포장한 예외의 의미를 담은 커스텀 예외를 만들어 던져주는 것도 하나의 방법이 될 수 있다.
이렇게 되면 의미가 분명한 예외로 전달해서 처리할 수 있게 된다.
try {
//...
} catch (SQLException e) {
throw DuplicatePieceIdException(e);
}
try {
//...
} catch (SQLException e) {
throw DuplicatePieceIdException().initCause(e);
}
또 자료에 따르면 JDBCTemplate을 사용하면 이 내부에서 SQLException을 처리해준다고 하니 차차 개념을 알아가자.
참고 자료
- JDBC를 이용한 DB작업_DB 연동 및 데이터 작업 2/3
- [개발자의 실수를 줄여주는 java.sql.Connection 만들기](