인프런/스프링 입문

10) 스프링 JdbcTemplate,RowMapper,lambda

backend dev 2022. 11. 28.

이전에는 순수한 jdbc로 코드를 짜는것을 보았다. 

 

이제는 jdbc -> jdbcTemplate로 바꿔서 어떻게 간결해졌는지 살펴보자.

 

JdbcTemplate

순수 jdbc와 동일한 환경설정을 하면 된다.

 

datasource를 스프링빈에 등록하는 과정

이렇게 해준다.

 

순수 jdbc의 불필요한 반복적인 코드를 없애 간결하게 만든거라고 생각하면된다.

 

 

JdbcTemplateMemberRepository.java

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public Member save(Member member) {
        return null;
    }

    @Override
    public Optional<Member> findById(Long id) {
        return Optional.empty();
    }

    @Override
    public Optional<Member> findByName(String name) {
        return Optional.empty();
    }

    @Override
    public List<Member> findAll() {
        return null;
    }
}

이렇게 구성해준다.

private final JdbcTemplate jdbcTemplate;

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

생성자를 만들때 매개변수로 datasource를 받고(@Autowired를 이용하여, 스프링 빈에 등록된 datasource를 가져온다.)

그 datasource를 jdbctemplate 객체를 생성할때 사용한다.

 

 

 

findByid를 구현하기전 jdbcTemplate의 query를 이용하려면 sql문과 RowMapper가 전달인자로 필요하다.

RowMapper는 ResultSet으로 받은 sql의 결과값들을 어떻게 객체와 매핑할건지에 대해 정리하는 인터페이스 객체라고 생각하면 된다.

RowMapper 생성, 구현 ,람다

private RowMapper<Member> memberRowMapper() {
    return new RowMapper<Member>() {
        @Override
        public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        }
    };
}

이렇게 구현가능.

하지만 RowMapper위에다가 Alt + Enter -> Replace Lambda로 하면 람다로 깔끔하게 바뀐다.

private RowMapper<Member> memberRowMapper() {
    return (rs, rowNum) -> {
        Member member = new Member();
        member.setId(rs.getLong("id"));
        member.setName(rs.getString("name"));
        return member;
    };
}

 

여기서 ResultSet(rs)은 SELECT의 결과를 저장하는 객체이다.

즉 ResultSet에 만약 Select * from user 의 결과가 저장되었다면 많은 수의 튜플(행) 이 있을 수 있다.

그때 int rowNumber는 말그대로 그 튜플의 수 (==카디널리티 == 데이터의 수 )를 의미하는것 같다.

 

 

이전 게시글에서 순수 jdbc를 보면 ResultSet에 결과값을 담고 

while을 이용하여 결과값을 foreach처럼 하나씩 가져와서 객체에 담고 리스트에 저장하는식이였는데

 

그 과정을 RowMapper의 mapRow에다가 오버라이드해서 사용하는 느낌이다.

그리고 RowMapper라는 인터페이스는 각 Row 즉 데이터에 Mapping(매핑)을 하기 위한 인터페이스이고

각 row마다 mapping은 mapRow 메소드를 구현해서 하는것같다.

 

https://hyunndyblog.tistory.com/28

 

JAVA - 익명 구현 객체, 람다식

1. 익명 구현 객체 - 보통 인터페이스, 구현 클래스, Main에서 구현 클래스의 객체를 써서 메소드를 호출하지만, 일회성의 구현 객체를 위해 소스 파일을 만들고 클래스를 선언하는것은 비효율적

hyunndyblog.tistory.com

 

다시 돌아가서

new RowMapper<Member>() {
        @Override
        public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        }
    };

 의 내부를 자세히보자.

ResultSet을 이용하여 sql결과를 가져오고 , rowNum을 이용해서 데이터의 갯수만큼 

Member~ return 까지의 작업을 반복해주는것 같다.

 

그렇게 구현한 memberRowMapper()는 jdbcTemplate.query의 두번째 매개변수로 전달된다.

@Override
public Optional<Member> findById(Long id) {
    List<Member> result = jdbcTemplate.query("select * from memeber where id = ?",
        memberRowMapper());
    return result.stream().findAny();
}

그리고 람다로 전환한 이 mapper를

private RowMapper<Member> memberRowMapper() {
    return (rs, rowNum) -> {
        Member member = new Member();
        member.setId(rs.getLong("id"));
        member.setName(rs.getString("name"));
        return member;
    };
}

 

더 간단하게 표현해본다면

private RowMapper<Member> memberRowMapper() {
    return (rs, rowNum) -> new Member(rs.getLong("id"), rs.getString("name"));
}

이렇게 가능하다.

물론 Member.java에서 생성자를 추가해줘야한다! ( 매개변수 id,name을 받고 생성하게끔)

public Member(Long id, String name) {
    this.id = id;
    this.name = name;
}

다음과같이

 

---

 

그렇다면 이렇게 람다식으로 짧게 적을 수 있으니 따로 RowMapper인터페이스 객체를 구현해서 사용하는것 보다

바로 매개변수로 RowMapper 람다식을 넣어버리는게 더 깔끔하다

 

하지만 해당 RowMapper가 자주 쓰일거같다면 따로 만들어둬서 쓰는게

더 깔끔한 코드 구성이 될 수 있다.

 

@Override
public Optional<Member> findById(Long id) {
    List<Member> result = jdbcTemplate.query("select * from memeber where id = ?",
        (rs, rowNum) -> new Member(rs.getLong("id"), rs.getString("name")),id);
    return result.stream().findAny();
}

최종적으로 다음과 같이까지 줄일 수 있다.

(sql문을 이용하여 가져온 결과 == Resultset을

2번째 전달인자로 주어진 RowMapper의 rowMap을 이용해서 각각 멤버 생성하고 가져온값 넣고

멤버를 멤버 리스트에 넣어준다)

 

여기까지가 findById를 구현하기

 

https://velog.io/@seculoper235/RowMapper%EC%97%90-%EB%8C%80%ED%95%B4

 


JdbcTemplateMemberRepository.java

save 구현

@Override
public Member save(Member member) {
    SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);//jdbcTemplate를 넘겨서 생성
    jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");//테이블명이랑 primary key를 전달해준다.
    
    Map<String, Object> parameters = new HashMap<>();
    parameters.put("name", member.getName());
    
    Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); //해당이름의 멤버를 저장하고 저장된 멤버의 키값가져온다.
    member.setId(key.longValue()); // 키값을 멤버에 넣고
    return member;// 리턴해줌
}

 

JdbcTemplateMemberRepository 전체코드

 

jdbc 쿼리마다 람다식을 넣는것보다는 

같은 람다식이 계속 쓰이므로  메소드화 해서 사용했따.

public class JdbcTemplateMemberRepository implements MemberRepository {

    private final JdbcTemplate jdbcTemplate;

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

    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);//jdbcTemplate를 넘겨서 생성
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");//테이블명이랑 primary key를 전달해준다.

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());

        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters)); //해당이름의 멤버를 저장하고 저장된 멤버의 키값가져온다.
        member.setId(key.longValue()); // 키값을 멤버에 넣고
        return member;// 리턴해줌
    }

    @Override
    public Optional<Member> findById(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id = ?",
            memberRowMapper(),id);
        return result.stream().findAny();
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?",
            memberRowMapper(),name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return jdbcTemplate.query("select * from member where name = ?",
            memberRowMapper());
    }

    private RowMapper<Member> memberRowMapper() {
        return (rs, rowNum) -> new Member(rs.getLong("id"), rs.getString("name"));
    }
}

 

 

테스트코드를 잘짜는 습관이 중요하다.

MemoryMemberRepository.java 처럼 서버가 꺼지면 데이터가 사라지는 테스트용 일회성 리포지토리를 만들어서

스프링을 올리지않고(=@SpringBootTest를)

내가 짠 코드들을 테스트해보고, 예외처리도 잘되는지 테스트해볼수있는 코드를 짜는게 중요하다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

댓글