[21강]
              
          2023. 2. 5. 17:44ㆍSpring 강의/section6
스프링 JdbcTemplate
순수 Jdbc와 동일한 환경설정을 하면 된다.
-> build.gradle에서 넣은 코드
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서의 반복 코드를 대부분 제거해준다.
하지만 sql은 직접 작성해야 한다.
먼저 repository - JdbcTemplateMemberRepository 클래스를 만든다.

순수 jdbc로 만들었을 때 이 길었던 코드를
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 
91 
92 
93 
94 
95 
96 
97 
98 
99 
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110 
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122 
123 
124 
125 
126 
127 
128 
129 
130 
131 
132 
133 
134 
135 
136 
137 
138 
139 
140 
 | 
 public class JdbcMemberRepository implements MemberRepository { 
    //db에 붙으려면 DataSource가 필요하다. 
    private final DataSource dataSource; 
    public JdbcMemberRepository(DataSource dataSource) {//스프링이 주입시켜준다. 
        this.dataSource = dataSource; 
    } 
    @Override 
    public Member save(Member member) { 
        String sql = "insert into member(name) values(?)"; 
        Connection conn = null; 
        PreparedStatement pstmt = null; 
        ResultSet rs = null; 
        try { 
            conn = getConnection(); 
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); 
            //id값 세팅 없이 저장할 때 자동으로 id값 생성시켜주는 옵션 
            pstmt.setString(1, member.getName()); 
            pstmt.executeUpdate(); //db에 쿼리가 날아간다. 
            rs = pstmt.getGeneratedKeys(); //RETURN_GENERATED_KEYS와 매칭해서 사용할 수 있다. 
                                           // db가 생성해준 키를 반환받을 수 있다. 
            if (rs.next()) { 
                member.setId(rs.getLong(1)); 
            } else { 
                throw new SQLException("id 조회 실패"); 
            } 
            return member; 
        } catch (Exception e) { 
            throw new IllegalStateException(e); 
        } finally { 
            close(conn, pstmt, rs); 
        } 
    } 
    @Override 
    public Optional<Member> findById(Long id) { 
        String sql = "select * from member where id = ?"; 
        Connection conn = null; 
        PreparedStatement pstmt = null; 
        ResultSet rs = null; 
        try { 
            conn = getConnection(); 
            pstmt = conn.prepareStatement(sql); 
            pstmt.setLong(1, id); 
            rs = pstmt.executeQuery(); //쿼리 조회하기 
            if(rs.next()) { 
                Member member = new Member(); 
                member.setId(rs.getLong("id")); 
                member.setName(rs.getString("name")); 
                return Optional.of(member); 
            } else { 
                return Optional.empty(); 
            } 
        } catch (Exception e) { 
            throw new IllegalStateException(e); 
        } finally { 
            close(conn, pstmt, rs); 
        } 
    } 
    @Override 
    public List<Member> findAll() { 
        String sql = "select * from member"; 
        Connection conn = null; 
        PreparedStatement pstmt = null; 
        ResultSet rs = null; 
        try { 
            conn = getConnection(); 
            pstmt = conn.prepareStatement(sql); 
            rs = pstmt.executeQuery(); 
            List<Member> members = new ArrayList<>(); 
            while(rs.next()) { 
                Member member = new Member(); 
                member.setId(rs.getLong("id")); 
                member.setName(rs.getString("name")); 
                members.add(member); 
            } 
            return members; 
        } catch (Exception e) { 
            throw new IllegalStateException(e); 
        } finally { 
            close(conn, pstmt, rs); 
        } 
    } 
    @Override 
    public Optional<Member> findByName(String name) { 
        String sql = "select * from member where name = ?"; 
        Connection conn = null; 
        PreparedStatement pstmt = null; 
        ResultSet rs = null; 
        try { 
            conn = getConnection(); 
            pstmt = conn.prepareStatement(sql); 
            pstmt.setString(1, name); 
            rs = pstmt.executeQuery(); 
            if(rs.next()) { 
                Member member = new Member(); 
                member.setId(rs.getLong("id")); 
                member.setName(rs.getString("name")); 
                return Optional.of(member); 
            } 
            return Optional.empty(); 
        } catch (Exception e) { 
            throw new IllegalStateException(e); 
        } finally { 
            close(conn, pstmt, rs); 
        } 
    } 
    private Connection getConnection() { 
        return DataSourceUtils.getConnection(dataSource); 
    } 
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) 
    { 
        try { 
            if (rs != null) { 
                rs.close(); 
            } 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        } 
        try { 
            if (pstmt != null) { 
                pstmt.close(); 
            } 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        } 
        try { 
            if (conn != null) { 
                close(conn); 
            } 
        } catch (SQLException e) { 
            e.printStackTrace(); 
        } 
    } 
    private void close(Connection conn) throws SQLException { 
        DataSourceUtils.releaseConnection(conn, dataSource); 
    } 
} 
 | 
cs | 
JdbcTemplate을 이용하여 단 2줄로 구현할 수 있다.
| 
 1 
2 
3 
4 
5 
 | 
     public Optional<Member> findById(Long id) { 
        List<Member> result = jdbcTemplate.query("select * from member where id =?",memberRowMapper()); 
        //query() 메서드는 sql 파라미터로 전달받은 쿼리를 실행하고 memberRowMapper를 이용해서 ResultSet의 결과를 자바 객체로 변환한다. 
        return result.stream().findAny(); 
    } 
 | 
cs | 
jdbctemplate에서 쿼리 날려서 결과를 rowmapper로 매핑하고 그것을 list로 받아서 optional로 바꿔서 반환한다.
쿼리문을 직접 작성하지 않아도 된다.
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
 | 
 public Member save(Member member) { 
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate); 
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); 
        Map<String, Object> parameters = new HashMap<>(); 
        parameters.put("name", member.getName()); 
        Number key = jdbcInsert.executeAndReturnKey(new 
                MapSqlParameterSource(parameters)); 
        member.setId(key.longValue()); 
        return member; 
    } 
 | 
cs | 
JdbcTemplateMemberRepository 전체 코드
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
68 
69 
70 
 | 
 package hello.hellospring2.repository; 
import hello.hellospring2.domain.Member; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.jdbc.core.JdbcTemplate; 
import org.springframework.jdbc.core.RowMapper; 
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; 
import org.springframework.jdbc.core.simple.SimpleJdbcInsert; 
import javax.sql.DataSource; 
import java.sql.ResultSet; 
import java.sql.SQLException; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import java.util.Optional; 
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); 
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id"); 
        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()); 
        //query() 메서드는 sql 파라미터로 전달받은 쿼리를 실행하고 memberRowMapper를 이용해서 ResultSet의 결과를 자바 객체로 변환한다. 
        return result.stream().findAny(); 
    } 
    @Override 
    public Optional<Member> findByName(String name) { 
        List<Member> result = jdbcTemplate.query("select * from member where name =?",memberRowMapper()); 
        return result.stream().findAny(); 
    } 
    @Override 
    public List<Member> findAll() { 
        return jdbcTemplate.query("select * from member",memberRowMapper()); 
    } 
    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; 
            } 
        }; 
    } 
} 
 | 
cs | 
20번 ~ 25번 줄은 스프링에서도 권장하는 방법이다.

이제 SpringConfig에서 조립해주면 사용할 수 있다.
| 
 1 
2 
3 
4 
5 
 | 
     @Bean 
    public MemberRepository memberRepository(){ 
        return new JdbcTemplateMemberRepository(dataSource); 
    } 
 | 
cs | 
실제 웹으로 돌릴 필요없이 지난 번에 만든 통합 test에서 돌리면 된다.
그리고 오류남

파라미터 설정에 관한 오류이다.

findById, findByName에서 마지막에 파라미터를 각각 id, name으로 넣어주면 된다.