영호

Jdbc와 Jdbc template비교 본문

Spring

Jdbc와 Jdbc template비교

0h0 2023. 4. 20. 19:04

기존 JDBC의 문제점

반복 코드가 너무 많다. 여기서 반복하는 작업은 아래와 같다.

  • DB연결을 위한 자원 명시하기
  • 파라미터 바인딩
  • 마지막에 자원 반납하기
    • 이 부분은 try with resource로 어느정도 해결이 가능하다.
  • 결과 바인딩

JDBC는 아래 코드 처럼 디비 연결, 파라미터 바인딩, 결과 변환 등등을 다른 쿼리를 날리는 메서드에서도 반복해야 한다.

public List<Long> findAllGameRooms() {
    String selectQuery = "SELECT game_room_id, status FROM game_room";
    try (Connection connection = dbConnection.getConnection()) {
        PreparedStatement preparedStatement = connection.prepareStatement(selectQuery);

        ResultSet resultSet = preparedStatement.executeQuery();
        List<Long> roomIds = new ArrayList<>();
        while (resultSet.next()) {
            roomIds.add(resultSet.getLong("game_room_id"));
        }
        return roomIds;
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

public boolean saveChessBoard(Map<Position, Piece> board, Side currentTurn, Long roomId) {
    final String saveQuery = "INSERT INTO pieces(piece_type, side, piece_rank, piece_file, game_room_id_fk) VALUES(?,?,?,?,?)";
    try (Connection connection = dbConnection.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(saveQuery);) {
        try {
            for (Map.Entry<Position, Piece> pieces : board.entrySet()) {
                File file = pieces.getKey().getFile();
                Rank rank = pieces.getKey().getRank();
                PieceType pieceType = pieces.getValue().getPieceType();
                Side pieceSide = pieces.getValue().getSide();

                preparedStatement.setString(1, pieceType.name());
                preparedStatement.setString(2, pieceSide.name());
                preparedStatement.setString(3, rank.getText());
                preparedStatement.setString(4, file.getText());
                preparedStatement.setLong(5, roomId);

                preparedStatement.executeUpdate();
            }
            connection.commit();
            return true;
        } catch (SQLException sqlException) {
            connection.rollback();
            throw new RuntimeException(sqlException);
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

public Connection getConnection() {
    try {
        Connection connection = DriverManager.getConnection("jdbc:mysql://" + SERVER + "/" + DATABASE + OPTION, USERNAME, PASSWORD);
        connection.setAutoCommit(false);
        return connection;
    } catch (final SQLException e) {
        System.err.println("DB 연결 오류:" + e.getMessage());
        e.printStackTrace();
        return null;
    }
}

JDBC Template

바로 코드를 보면서 비교해보겠다. 코드는 공식문서에서 가져왔다.

DB연결

@Repository 
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired 
    public JdbcCorporateEventDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource); 
    }
}

DB연결이 끝났다.이제 jdbcTemplate을 이용해 쿼리를 날릴 수 있다.

DataSouce는 디비 연결을 위한 정보(username, password등등)를 관리하는 객체다. 

파라미터 바인딩

String query = "insert into t_actor (first_name, last_name) values (?, ?)";
this.jdbcTemplate.update(query, "Leonor", "Watling");

기존 JDBC코드보다 파라미터 바인딩이 훨씬 깔끔해졌다.

그러나, 개인적으로 위 방식은 개발자가 파라미터를 잘못 바인딩할 위험이 있다고 생각한다. 그래서 Spring에서도 NamedParameterJdbcTemplate을 제공한다.

코드만 간단하게 보자면 아래와 같다.

String sql = "select count(*) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);

return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);

이런 식으로 명시적으로 파라미터를 바인딩 해 실수를 줄일 수 있다. 자세한 내용은 공식문서를 보자

결과 바인딩

private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
    Actor actor = new Actor();
    actor.setFirstName(resultSet.getString("first_name"));
    actor.setLastName(resultSet.getString("last_name"));
    return actor;
};

public List<Actor> findAllActors() {
    return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
}
  • RowMapper를 이용해 조회 결과를 기반으로 객체를 만들 수 있다.
    • 여기서 resultSet은 Jdbc와 마찬가지로 조회된 결과를 의미하고, rowNum은 조회된 데이터의 row개수를 의미한다.
  • query는 여러 건 조회, queryForObject를 사용하면 단건 조회가 가능해서 return이 List가 아닌 Actor가 된다.
  • 공식문서를 보면 @FunctionalInterface가 붙어있는데 실제 코드를 보면 해당 어노테이션은 없다.
    • RowMapper는 메서드가 하나만 존재하는 인터페이스라 람다 전달이 가능하다.

정리

궁금한 점

  • 공식문서에는 JdbcTemplate을 초기화 할 때 DataSource를 주입받는다. 그러나 Spring boot를 사용하면 명시적으로 주입 받을 필요가 없다.
    • 이유를 찾아보니 application.yml에 정의한 db관련 리소스를 바탕으로 Spring boot가 자동으로 DataSource빈을 생성한다.
    • 이후, JdbcTemplate에 주입해주기 때문에 별도로 DataSource객체를 생성하려고 신경쓰지 않아도 된다.
private final JdbcTemplate jdbcTemplate;

public GameDao(JdbcTemplate jdbcTemplate) {
    this.jdbcTemplate = jdbcTemplate;
}

 

Comments