Spring boot + JdbcTemplate 사용해보기 (3) Repository 구현

Peter·2024년 7월 13일
0
post-thumbnail

JdbcTemplate DI

JdbcTemplate 의 의존성 주입의 경우, 아래와 같이 constuctor 에 DataSource 를 받아서 JdbcTemplate 인스턴스를 생성해주는 방식이 관례라고 한다.

물론 별도의 설정파일에서 JdbcTemplate 을 Bean 으로 등록해 놓고 사용해도 된다.

@Repository
public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate template;

    public JdbcTemplateMemberRepository(DataSource dataSource) {
        this.template = new JdbcTemplate(dataSource);
    }
    
    // 다른 코드 생략
}

save

JdbcTemplate 을 이용하면 원래 더 간단하게 코드를 작성할 수 있는 데, 아래 코드는 기대했던 것 보다 장황하다.
코드가 장황하게 된 이유는 keyHolder 때문이다.
member 를 insert 하고 난 뒤 member 에 할당된 id 를 가져오기 위해 keyHolder 가 사용되었다.

@Override
public Member save(Member member) {
    String sql = "INSERT INTO members (name, gender, position, birth_date, address, phone_number)"
        + " VALUES (?,?,?,?,?,?)";
    GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
    template.update(connection -> {
        // 자동 증가 키
        PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"});
        ps.setString(1, member.getName());
        ps.setString(2, member.getGender().name());
        ps.setString(3, member.getPosition());
        ps.setDate(4, Date.valueOf(member.getBirthDate()));
        ps.setString(5, member.getAddress());
        ps.setString(6, member.getPhoneNumber());
        return ps;
    }, keyHolder);

    long key = keyHolder.getKey().longValue();
    member.setId(key);
    return member;
}
  • prepareStatement 메서드의 두 번째 파라미터인 new String[]{"id"} 는 DB 에서 자동생성되는 key 들의 배열이다. members DB 에서 id 가 AUTO_INCREMENT 로 설정되어 있기 때문에 파라미터가 이와 같이 설정되었다.
    (아래 API 문서 참고)

update

save 에 비해 update 는 비교적 심플하다.
JdbcTemplate 의 update 메서드를 호출하면 되고,
첫번째 파라미터로 sql 을 넘겨주고 그 이후의 파라미터들을 sql 에 표시된 ? 순서대로 넘겨주면 된다.
(순서대로 파라미터를 넘겨줘야 한다는 것이 단점이다. 컬럼추가되면.. 순서 때문에 실수할 확률이 매우 높다.. ㅠㅠ)

@Override
public void update(Long memberId, MemberUpdateParam updateParam) {
    String sql = "UPDATE members SET name = ?"
        + ", gender = ?"
        + ", position = ?"
        + ", birth_date = ?"
        + ", address = ?"
        + ", phone_number = ?"
        + " WHERE id = ?";
    template.update(sql, updateParam.getName(), updateParam.getGender().name(), updateParam.getPosition(),
        updateParam.getBirthDate(), updateParam.getAddress(), updateParam.getPhoneNumber(), memberId);
}

findById

select 의 경우, JdbcTemplate 메서드 중 queryXXX 형태로 정의된 것들을 사용하면 된다.
여기서는 queryForObject 메서드가 사용되었다.
쿼리 결과를 특정 객체로 mapping 해주도록 할 수 있는 데, 그 역할을 하는 것이 memberRowMapper() 이다.

@Override
public Optional<Member> findById(Long id) {
    String sql = "SELECT id, name, gender, position, birth_date, address, phone_number FROM members WHERE id=?";
    try {
        Member member = template.queryForObject(sql, memberRowMapper(), id);
        return Optional.ofNullable(member);
    } catch (EmptyResultDataAccessException e) {
        return Optional.empty();
    }
}

private RowMapper<Member> memberRowMapper() {
    return ((rs, rowNum) -> Member.builder()
        .id(rs.getLong("id"))
        .name(rs.getString("name"))
        .gender(Gender.valueOf(rs.getString("gender")))
        .position(rs.getString("position"))
        .birthDate(rs.getDate("birth_date").toLocalDate())
        .address(rs.getString("address"))
        .phoneNumber(rs.getString("phone_number"))
        .build());
}
  • queryForObject 메서드는 아래와 같이 정의되어 있다.
    sql 이랑 sql 에 사용된 ? 에 대한 파라미터들을 args 로 받고 있고, rowMapper 도 파라미터로 받고 있다.

    위 메서드에서 사용된 nullableSingleResult 메서드는 아래와 같이 정의되어 있다.

    쿼리 결과가 row 가 1개가 아니면 (즉, 없거나 2개 이상이면) 예외를 throw 하는 것을 확인 할 수 있다.
    물론 우리가 작성한 로직의 경우, id 가 PK 이기 때문에 조회 결과가 없거나 1개일 것이라서 EmptyResultDataAccessException 에 대한 예외 처리만 해줬다.

  • RowMapper<T> 의 경우, (ResultSet rs, int rowNum) -> T 함수에 대한 FunctionalInterface 이다.
    memberRowMapper() 코드는 결국, ResultSet 에 담긴 데이터를 Member 객체의 프로퍼티로 매핑해서 Member 객체를 생성하여 리턴해주는 역할을 한다.

findAll

findAll 의 경우도 아래 코드는 비교적 간단하다.
하지만, 검색 파라미터가 1개 라서 간단한 것이지 검색 파라미터가 늘어날 수록 코드의 복잡도는 기하급수적으로 복잡해질 것이다.
JdbcTemplate 이 동적 쿼리 작성하기에는 확실히 난이도가 높은 것 같다.

@Override
public List<Member> findAll(MemberSearchParam cond) {
    String name = cond.getName();

    String sql = "SELECT id, name, gender, position, birth_date, address, phone_number FROM members";
    List<Object> param = new ArrayList<>();

    if (StringUtils.hasText(name)) {
        sql += " WHERE name Like CONCAT('%',?,'%')";
        param.add(name);
    }

    return template.query(sql, memberRowMapper(), param.toArray());
}

delete

제일 간단하다. (자세한 설명은 생략한다)

@Override
public void delete(Long memberId) {
    String sql = "DELETE FROM members WHERE id=?";
    template.update(sql, memberId);
}

0개의 댓글