티스토리 뷰

초보 웹 개발자를 위한 스프링 5 프로그램 입문을 보고 복습 겸 개인 학습 정리입니다. (windows 기준)

 

 

 

 

jdbcTemplate

스프링을 사용하면 DataSource나 Connetion, Statemnt, ResultSet을 직접 사용하지 않고 JdbcTemplate을 이용해서 편하게 쿼리를 실행할 수 있다.

 

 

query() 메서드

jdbcTemplate 클래스는 SETECT 쿼리 실행을 위한 query() 메서드를 제공한다.

query() 메서드는 sql 파라미터로 전달받은 쿼리를 실행 → RowMapper를 이용해서 ResultSet의 결과를 자바 객체로 변환

sql 파라미터가  (select * from member where email =?) 인덱스 기반의 파라미터를 가진 쿼리이면 args 파라미터를 이용해 각 인덱스 파라미터 값을 지정한다.

 

자주 사용되는 쿼리 메서드

- List<T> query(String sql, RowMapper<T> rowMapper)

- List<T> query(String sql, Object [] args, RowMapper rowMapper)

- List<T> query(String sql, Object [] args, RowMapper rowMapper, Object.... args)

 

쿼리 실행 결과를 자바 객체로 변환할 때 사용하는 RowMapper 인터페이스는 아래와 같은데 이 mapRow() 메서드는 ResultSet에서 한 행의 데이터를 읽어와 자바 객체로 변환하는 매퍼 기능을 구현한다.

package org.springframework.jdbc.core;

public interface RowMapper<T> {
	T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

 

 

MemberDao 클래스의 selectByEmail 메서드에 query() 메서드를 사용했다.

→sp5-chap08/src/main/java/spring/MemberDao.java

package spring;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class MemberDao {

	private JdbcTemplate jdbcTemplate;

	public MemberDao(DataSource dataSource) {
		this.jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public Member selectByEmail(String email) { // query()메서드를 이용해 쿼리 실행 
		List<Member> results = jdbcTemplate.query(
			"select*from MEMBER where EMAIL = ?", 
			new RowMapper<Member>() {
				@Override // mapRow 사용
				public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
					Member member = new Member (
						rs.getString("EMAIL"),
						rs.getString("PASSWORD"),
						rs.getString("NAME"),
						rs.getTimestamp("REGADATE").toLocalDateTime());
					member.setId(rs.getLong("ID"));
					return member;
				}
			}, email);

			/* *람다 표현식
			List<Member> results = jdbcTemplate.query
			"select*from MEMBER where EMAIL = ?",
			(ResultSet rs, int rowNum) -> {
				Member member = new Member (
					rs.getString("EMAIL"),
					rs.getString("PASSWORD"),
					rs.getString("NAME"),
					rs.getTimestamp("REGADATE").toLocalDateTime());	
				member.setId(rs.getLong("ID"));
				return member;
			},
			email);
				*/
		// query() 메서드는 실행한 결과가 존재하지 않으면 길이가 0 인 list를 리턴하므로 
		// list가 비어있는지 여부로 결과를 확인 할 수 있다.
		return results.isEmpty() ? null : results.get(0); 
	}

	public void insert(Member member) {
	}

	public void update(Member member) {
	}
}

 

queryForObject() 메서드

queryForObject() 메서드는 쿼리 실행 결과 행(가로)이 한 개인 경우에 사용할 수 있는 메서드.

만약 쿼리 실행 결과 행이 없거나 두 개 이상이면 IncorretResultSizeDataAccessException 또는 EmptyResultSizeDataAccessException이 발생하기 때문에 행이 정확히 한 개가 아닌 경우 query() 메서드를 사용해야 한다.

 

아래 코드는 MEMBER 테이블의 전체 행 개수를 구하는 코드이다.

public Member count(String email) {
		List<Member> results = jdbcTemplate.query(
			"select count(*) from MEMBER", 
			new RowMapper<integer>() { // <> 안의 값 integer로 바꿈
				@Override
				public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                			return rs.getInt(1);
				}
		});
        return results.get(0);
 }

"select count(*)..."의 count(*) 쿼리결과가 한 행이기 때문에 결괏값을 List로 받는 것보다 integer로 받는 것이 좋기 때문에 아래와 같이 queryForObject 메서드를 이용하여 바꿔주었다.

 

→sp5-chap08/src/main/java/spring/MemberDao.java

public int count() {
	Integer count = jdbcTemplate.queryForObject(
			"select count(*) from MEMBER", Integer.class);
	return count;                  //  평균을 구한다면 Double.class 타입을 사용할 수 있다.
}

★ 여기서 query() 메서드를 이용한 코드와 queryForObject() 메서드를 이용한 코드의 차이점은 리턴 타입이 List가 아니라 RowMapper로 변환해 주는 타입이라는 점이다.

 

 

주요 queryForObject() 메서드

- T queryForObject(String sql, Class <T> requiredType)

- T queryForObject(String sql, Class <T> requiredType, Object... args)

- T queryForObject(String sql, RowMapper <T> rowMapper)

- T queryForObject(String sql, RowMapper <T> rowMapper, Object... args)

 

 

jdbcTemplate을 이용한 변경 쿼리 실행

query() 메서드는 데이터 테이블의 데이터를 불러오는 메서드였다면 데이터 INSERT, UPDATE, DELETE(삽입, 삭제, 업데이트) 쿼리는 update() 메서드를 사용한다. 

 

 

update() 메서드는 쿼리 실행 결과로 변경된 행의 개수를 리턴한다. 생성된 키값을 리턴하진 않는다.

※ 생성된 키값을 구할 수 있는 방법은 KeyHolder를 사용하면 된다.

 

- int update(String sql)

- int update(String sql, Object... args)

 

MemberDao 클래스에 update() 메서드를 추가하자.

 

→sp5-chap08/src/main/java/spring/MemberDao.java

public void update(Member member) {
    jdbcTemplate.update("update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?", 
                member.getName(), member.getPassword(), member.getEmail());
}

 

KeyHolder

생성된 키값을 구하기 위해 KeyHolder를 사용하면 객체의 ID값을 구할 수 있다.

 

MemberDao클래스에 keyHolder를 삽입해 보자.

→sp5-chap08/src/main/java/spring/MemberDao.java

public void insert(Member member) {
    KeyHolder keyHolder = new GeneratedKeyHolder();
    jdbcTemplate.update(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection con)
                throws SQLException {
            // 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
            PreparedStatement pstmt = con.prepareStatement(
                    "insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) " +
                    "values (?, ?, ?, ?)",
                    new String[] { "ID" });
            // 인덱스 파라미터 값 설정
            pstmt.setString(1, member.getEmail());
            pstmt.setString(2, member.getPassword());
            pstmt.setString(3, member.getName());
            pstmt.setTimestamp(4,
                    Timestamp.valueOf(member.getRegisterDateTime()));
            // 생성한 PreparedStatement 객체 리턴
            return pstmt;
        }
    }, keyHolder);
    Number keyValue = keyHolder.getKey();
    member.setId(keyValue.longValue());
}

 

MainForMemberDao class 실행

AppCtx 클래스 설정을 사용하는 메인 클래스(MainForMemberDao)를 작성하고 실행해 보자.

 

→sp5-chap08/src/main/java/main/MainForMemberDao.java

package main;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import config.AppCtx;
import spring.Member;
import spring.MemberDao;

public class MainForMemberDao {
    private static MemberDao memberDao;
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);

        memberDao = ctx.getBean(MemberDao.class);

        selectAll();
        updateMember();
        insertMember();

        ctx.close();
    }

    public static void selectAll() {
        System.out.println("--------------selectAll");
        int total = memberDao.count();
        System.out.println("전체 데이터 : " + total);
        List<Member> members = memberDao.selectAll();
        for (Member m : members) {
            System.out.println(m.getId()+"  :  "+m.getEmail()+"  :  "+m.getName());
        }
    }

    private static void updateMember() {
		System.out.println("--------------updateMember");
		Member member = memberDao.selectByEmail("a@a.com");
		String oldPw = member.getPassword();
		String newPw = Double.toHexString(Math.random());
		member.changePassword(oldPw, newPw);

		memberDao.update(member);
		System.out.println("암호 변경: " + oldPw + " -> " + newPw);
	}

	private static DateTimeFormatter formatter = 
			DateTimeFormatter.ofPattern("MMddHHmmss");

	private static void insertMember() {
		System.out.println("--------------insertMember");

		String prefix = formatter.format(LocalDateTime.now());
		Member member = new Member(prefix + "@test.com", 
				prefix, prefix, LocalDateTime.now());
		memberDao.insert(member);
		System.out.println(member.getId() + "  : 데이터 추가");
	}
}

 

메인 클래스를 실행하면 차례대로 메서드가 실행되는 것을 확인할 수 있다.

위에서 설정한 KeyHolder (ID값)도 가져오는 것을 볼 수 있다.

 

selectAll();                 → member 테이블의 전체 데이터 값을 가져온다. for문 사용 ID값, 이메일, 이름 순으로 나열됨
updateMember();  → selectByEmail()의 특정 이메일을 가진 회원의 비밀번호를 새 비밀번호로 업데이트한다.
insertMember();     → 새로운 회원 정보를 추가한다.

ctx.close();            → 스프링 빈 객체 소멸

 

main 클래스 실행 결과 값

 

MySQL member table 값

 

'study > Spring' 카테고리의 다른 글

Tomcat 설치  (0) 2023.01.25
chap 08 - 트랜잭션 처리 (Transaction)  (0) 2023.01.25
chap 08 - DB 연동 DataSource 설정  (1) 2023.01.13
chap 07 - AOP 프로그래밍  (0) 2023.01.10
chap 06 - 빈 라이프사이클 & 범위  (0) 2023.01.09
댓글