Luver Duck 2022. 9. 8. 00:38

JdbcTemplate 클래스를 이용한 CRUD 기능 구현

준비. Model이 될 클래스 정의 (DTO 또는 VO)

1. Dao(Interface 형태)에서 추상 메소드 선언

2. DaoImpl(class 형태)에서 추상 메소드 구현

3. Controller에서 Mapping 설정

준비 - Model이 될 클래스 정의 (DTO 또는 VO)

- MUSIC 테이블 컬럼명 확인

 

- DTO 클래스 생성 : 필드, 생성자, getter&setter 포함 (toString 오버라이딩은 선택 사항)

package com.kh.springhome.entity;

public class PocketMonsterDto {

	// 필드 - 컬럼의 자료형과 동일하게
	private int no;
	private String name;
	private String type;
	
	// 생성자
	public PocketMonsterDto() {
		super();
	}
	
	// getter & setter
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}

	// toString 오버리이딩 (선택사항)
	@Override
	public String toString() {
		return "PocketMonsterDto [no=" + no + ", name=" + name + ", type=" + type + "]";
	}
}

 

MUSIC 테이블 - 상세 조회(DETAIL)

1. Dao(Interface 형태)에서 추상 메소드 선언

- 상세 조회(DETAIL)는 명령 실행 후 하나의 조회 결과를 DTO의 형태로 반환한다

- primary key인 음원 번호(musicNo)를 이용하여 상세 조회(DETAIL) 명령을 실행한다

package com.kh.springhome.repository;

import com.kh.springhome.entity.MusicDto;

public interface MusicDao {

	// 추상 메소드 - 상세 조회(DETAIL)
	MusicDto selectOne(int musicNo);
}

 

2. DaoImpl(class 형태)에서 추상 메소드 구현

- SQL문을 실행하기 위해 가장 먼저 JdbcTemplate 클래스의 인스턴스를 스프링 IoC 컨테이너에 등록해야 한다

- 상세 조회(DETAIL) 결과를 반환하기 위한 ResultSetExtractor가 반드시 필요하다

 

- 검색 조회(selectList) 메소드 구현

1) SQL문 생성

2) SQL문의 바인드 변수에 들어갈 매개변수 배열을 생성한다

3) JdbcTemplate의 query(String sql, ResultSetExtractor<T> rse, Object... args) 메소드를 실행한다

package com.kh.springhome.repository;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.stereotype.Repository;

import com.kh.springhome.entity.MusicDto;

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	// 상세 조회(DETAIL) 결과를 반환하기 위한 ResultSetExtractor 
	private ResultSetExtractor<MusicDto> extractor = new ResultSetExtractor<>() {
		@Override
		public MusicDto extractData(ResultSet rs) throws SQLException, DataAccessException {
			if(rs.next()) {
				MusicDto musicDto = new MusicDto();
				musicDto.setMusicNo(rs.getInt("music_no"));
				musicDto.setMusicTitle(rs.getString("music_title"));
				musicDto.setMusicArtist(rs.getString("music_artist"));
				musicDto.setMusicAlbum(rs.getString("music_album"));
				musicDto.setMusicPlay(rs.getInt("music_play"));
				musicDto.setReleaseTitle(rs.getDate("release_title"));
				return musicDto;
			}
			else {
				return null;	
			}
		}
	};
	
	// 추상 메소드 오버라이딩 - 상세 조회(DETAIL)
	@Override
	public MusicDto selectOne(int musicNo) {
		// 1) SQL문 생성
		String sql = "select * from music where music_no = ?";
		// 2) SQL문의 바인드 변수에 들어갈 매개변수 배열 생성
		Object[] param = new Object[] {musicNo};
		// 3) JdbcTemplate의 query(String sql, ResultSetExtractor<T> rse, Object... args) 메소드 실행 실행
		return jdbcTemplate.query(sql, extractor, param);
	}
}

 

3. Controller에서 Mapping 설정

1) 상세 조회 Mapping

- 상세 조회 Mapping으로 이동하기 위해서는 int 형태의 musicNo라는 requestParam이 필요하다

- 따라서 localhost:포트번호/music/detail?musicNo=음원번호 인 URL로 접속할 때

  /WEB-INF/views/music/detail.jsp 경로에 있는 view를 띄우도록 주소를 Mapping한다

  ( ** 전체 목록에서 상세보기로 이동할 때 <a> 태그의 href 속성으로 requestParam을 부여할 수 있다 )

package com.kh.springhome.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.kh.springhome.entity.MusicDto;
import com.kh.springhome.repository.MusicDao;

@Controller
@RequestMapping("/music")
public class MusicController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;

	// 상세 조회 Mapping
	@GetMapping("/detail")
	public String detail(Model model, @RequestParam int musicNo) {
		// Model에 상세 조회(DETAIL) 결과의 MusicDto를 포함 
		model.addAttribute("musicDto", musicDao.selectOne(musicNo));
		return "music/detail";
	}
}

 

2) View에서 표시 형식 정의

- Controller로부터 전달받은 Model을 이용하여 View에 표시할 형식을 정한다

- Model에는 musicDto라는 이름의 MusicDto의 인스턴스 하나가 포함되어 있다

- <fmt></fmt>태그의 formatDate를 사용하면 DB상에 표시되지 않는 상세 날짜 정보를 출력할 수 있다

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<tr>
	<th width = "25%">발매일</th>		
	<td>
		<fmt:formatDate value="${musicDto.releaseTitle}" pattern = "y년 M월 d일 E요일"/>
	</td>
</tr>

 

- 완성된 JSP

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>음원 상세 정보</title>
</head>
<body>
	<div align="center">
	<h1>${musicDto.getMusicNo()}번 음원 정보</h1>

	<table border = "1" width = "600">
		<tbody align = "center">
				<tr>
					<th width = "25%">음원 번호</th>
					<td>${musicDto.getMusicNo()}</td>
				</tr>
				<tr>
					<th width = "25%">음원명</th>
					<td>${musicDto.getMusicTitle()}</td>
				</tr>
				<tr>
					<th width = "25%">가수명</th>
					<td>${musicDto.getMusicArtist()}</td>
				</tr>
				<tr>
					<th width = "25%">앨범명</th>
					<td>${musicDto.getMusicAlbum()}</td>
				</tr>
				<tr>
					<th width = "25%">재생수</th>
					<td>${musicDto.getMusicPlay()}</td>
				</tr>
				<tr>
					<th width = "25%">발매일</th>		
					<td>
						<fmt:formatDate value="${musicDto.releaseTitle}" pattern = "y년 M월 d일 E요일"/>
					</td>
				</tr>
			</tbody>
	</table>
</div>
</body>
</html>

 

MUSIC 테이블 - 수정(UPDATE)

1. Dao(Interface 형태)에서 추상 메소드 선언

- 수정(UPDATE)는 명령 실행 후 수정 여부(레코드 수정 갯수가 0 이상인지)를 반환해야 하므로 반환형은 boolean이 된다

- primary key인 음원 번호(musicNo)를 이용하여 수정(UPDATE) 명령을 실행한다

- 수정 페이지에 수전 전 값을 표시하기 위해서는 Controller에서 단일 조회의 결과를 View로 보내야 한다

package com.kh.springhome.repository;

import java.util.List;

import com.kh.springhome.entity.MusicDto;
import com.kh.springhome.vo.MusicYearCountVO;

public interface MusicDao {

	// 추상 메소드 - 상세 조회(DETAIL)
	MusicDto selectOne(int musicNo);

	// 추상 메소드 - 수정(UPDATE)
	boolean update(MusicDto musicDto);
}

 

2. DaoImpl(class 형태)에서 추상 메소드 구현

1) SQL문 생성

2) SQL문의 바인드 변수(?)에 들어갈 매개변수 배열 생성

3) JdbcTemplate의 update(String sql, Object... args) 메소드 실행

package com.kh.springhome.repository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import com.kh.springhome.entity.MusicDto;

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;

	// 상세 조회(DETAIL) 결과를 반환하기 위한 ResultSetExtractor 
	private ResultSetExtractor<MusicDto> extractor = new ResultSetExtractor<>() {
		@Override
		public MusicDto extractData(ResultSet rs) throws SQLException, DataAccessException {
			if(rs.next()) {
				MusicDto musicDto = new MusicDto();
				musicDto.setMusicNo(rs.getInt("music_no"));
				musicDto.setMusicTitle(rs.getString("music_title"));
				musicDto.setMusicArtist(rs.getString("music_artist"));
				musicDto.setMusicAlbum(rs.getString("music_album"));
				musicDto.setMusicPlay(rs.getInt("music_play"));
				musicDto.setReleaseTitle(rs.getDate("release_title"));
				return musicDto;
			}
			else {
				return null;	
			}
		}
	};
	
	// 추상 메소드 오버라이딩 - 상세 조회(DETAIL)
	@Override
	public MusicDto selectOne(int musicNo) {
		// 1) SQL문 생성
		String sql = "select * from music where music_no = ?";
		// 2) SQL문의 바인드 변수에 들어갈 매개변수 배열 생성
		Object[] param = new Object[] {musicNo};
		// 3) JdbcTemplate의 query(String sql, ResultSetExtractor<T> rse, Object... args) 메소드 실행 실행
		return jdbcTemplate.query(sql, extractor, param);
	}

	// 추상 메소드 오버라이딩 - 수정(UPDATE)
	@Override
	public boolean update(MusicDto musicDto) {
		// 1) SQL문 생성
		String sql = "update music set music_title = ?, " 
							+ "music_artist = ?, " 
							+ "music_album = ?, " 
							+ "music_play = ?, " 
							+ "release_title = ? " 
						+ "where music_no = ?";
		// 2) SQL문의 바인드 변수에 들어갈 매개변수 배열 생성
		Object[] param = new Object[] {musicDto.getMusicTitle(), 
						musicDto.getMusicArtist(), 
						musicDto.getMusicAlbum(), 
						musicDto.getMusicPlay(), 
						musicDto.getReleaseTitle(), 
						musicDto.getMusicNo()};
		// 3) JdbcTemplate의 update(String sql, Object... args) 메소드 실행
		return jdbcTemplate.update(sql, param) > 0;
	}
}

 

3. Controller에서 Mapping 설정

1) 수정 Mapping (미완성 Controller)

- 상세 조회와 마찬가지로 수정 Mapping으로 이동하기 위해서는 int 형태의 musicNo라는 requestParam이 필요하다

- 따라서 localhost:포트번호/music/update?musicNo=음원번호 인 URL로 접속할 때

  /WEB-INF/views/music/update.jsp 경로에 있는 view를 띄우도록 주소를 Mapping한다

  ( ** 전체 목록에서 상세 조회로 이동할 때 <a> 태그의 href 속성으로 requestParam을 부여할 수 있다 )

- localhost:포트번호/music/update?musicNo=음원번호 인 URL로 접속할 때

  musicNo를 이용하여 상세 조회(DETAIL)를 실행한 후 그 결과를 Model에 추가하여 View로 보낸다

package com.kh.springhome.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.kh.springhome.entity.MusicDto;
import com.kh.springhome.repository.MusicDao;

@Controller
@RequestMapping("/music")
public class MusicController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 수정(UPDATE)
	// 1) 수정 Mapping
	@GetMapping("/edit")
	public String edit(Model model, @RequestParam int musicNo) {
		// Model에 상세 조회(DETAIL) 결과의 MusicDto를 포함
		model.addAttribute("musicDto", musicDao.selectOne(musicNo));
		return "music/edit";
	}

 

2) View에서 표시 형식 정의

- View에서 값을 입력한 후 update 페이지에 post 방식으로 전송한다

- <form>의 action 속성은 값을 전송할 주소, method 속성은 전송 방식을 의미한다

- @RequestParam 또는 @ModelAttribute 어노테이션을 붙여 전송한 값을 받을 수 있다

- 수정하지 않을 정보(musicNo, musicPlay)는 <input>태그의 type을 hidden으로 하여 사용자 입력을 막아야 한다

- 수정할 정보에 수정전 값이 표기되도록 하기 위해서는 <input> 태그의 value를 Model의 값으로 하면 된다

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>음원 정보 변경</title>
</head>
<body>
	<div align = "center">
		<h1>음원 정보 변경</h1>
		<form action = "edit" method = "post">
			<input type = "hidden" name = "musicNo" value = "${musicDto.getMusicNo()}">
			<input type = "text" name = "musicTitle" value = "${musicDto.getMusicTitle()}"><br><br>
			<input type = "text" name = "musicArtist" value = "${musicDto.getMusicArtist()}"><br><br>
			<input type = "text" name = "musicAlbum" value = "${musicDto.getMusicAlbum()}"><br><br>
 			<input type = "hidden" name = "musicPlay" value = "${musicDto.getMusicPlay()}"><br><br>						
			<input type = "date" name = "releaseTitle" value = "${musicDto.getReleaseTitle()}"><br><br>
			<button type = "submit">변경</button>
		</form>
	</div>
</body>
</html>

 

3) DB 처리 및 redirect

- View에서 POST로 전송받은 데이터를 통해 DB 처리 수행

- 매개변수에 @RequestParam 또는 @ModelAttribute 어노테이션을 붙여 View에서 전송한 값을 받을 수 있다

- 수정(UPDATE) 결과(boolean, 0보다 큰지)에 따라 서로 다른 Mapping으로 이동

 

- RedirectAttributes

addAttribute(String attributeName, Object attributeValue) redirect시 RequestParam으로 사용할 변수명과 그 값을 추가

 

package com.kh.springhome.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.kh.springhome.entity.MusicDto;
import com.kh.springhome.repository.MusicDao;

@Controller
@RequestMapping("/music")
public class MusicController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 수정(UPDATE)
	// 1. 수정 Mapping
	@GetMapping("/edit")
	public String edit(Model model, @RequestParam int musicNo) {
		// Model에 상세 조회(DETAIL) 결과의 MusicDto를 포함
		model.addAttribute("musicDto", musicDao.selectOne(musicNo));
		return "music/edit";
	}
	
	// 2. View에서 수정 Mapping으로 DTO 전달 및 DB 처리 
	@PostMapping("/edit")
	public String edit(@ModelAttribute MusicDto musicDto, RedirectAttributes attr) {
		// 수정(UPDATE) 실행 결과(boolean, 실행 결과가 0보다 큰지)를 반환
		boolean result = musicDao.update(musicDto);
		// 실행 결과에 따라 서로 다른 Mapping으로 이동
		if(result) {	// true라면 해당 번호(musicNo) 음원의 단일 조회 Mapping으로 이동
			attr.addAttribute("musicNo", musicDto.getMusicNo());
			return "redirect:detail";
		}
		else {	// false라면 수정 실패(edit_fail) Mapping으로 이동
			return "redirect:edit_fail";
		}
	}
	 
	// 3. 수정 실패 Mapping
	@GetMapping("/edit_fail")
	public String editFail() {
		// 수정 실패 페이지(editFail.jsp)으로 이동
		return "music/editFail";
	}
}

 

MUSIC 테이블 - 삭제(SELECT)

1. Dao(Interface 형태)에서 추상 메소드 선언

- 삭제(DELETE)는 명령 실행 후 삭제 여부(레코드 수정 갯수가 0 이상인지)를 반환해야 하므로 반환형은 boolean이 된다

- primary key인 음원 번호(musicNo)를 이용하여 삭제(DELETE) 명령을 실행한다

package com.kh.springhome.repository;

import java.util.List;

import com.kh.springhome.entity.MusicDto;

public interface MusicDao {

	// 추상 메소드 - 삭제(DELETE)
	boolean delte(int musicNo);
}

 

2. DaoImpl(class 형태)에서 추상 메소드 구현

1) SQL문 생성

2) SQL문에 바인드 변수가 없으므로 매개변수 배열은 생성할 필요가 없다

3) JdbcTemplate의 update(String sql, Object... args) 메소드 실행

package com.kh.springhome.repository;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class MusicDaoImpl implements MusicDao {

	// 의존성 주입
	@Autowired
	JdbcTemplate jdbcTemplate;
	
	// 추상 메소드 오버라이딩 - 삭제(DELETE)
	@Override
	public boolean delte(int musicNo) {
		String sql = "delete music where music_no = ?";
		return jdbcTemplate.update(sql, musicNo) > 0;
	}
}

 

3. Controller에서 Mapping 설정

 

package com.kh.springhome.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.kh.springhome.entity.MusicDto;
import com.kh.springhome.repository.MusicDao;

@Controller
@RequestMapping("/music")
public class MusicController {

	// 의존성 주입
	@Autowired
	private MusicDao musicDao;
	
	// 삭제(DELETE)
	// 1. 삭제 Mapping
	@GetMapping("/delete")
	public String delete(@RequestParam int musicNo) {
		// 1) 삭제(DELETE) 실행 결과(boolean, 실행 결과가 0보다 큰지)를 반환
		boolean result = musicDao.delte(musicNo);
		// 2) 실행 결과에 따라 서로 다른 Mapping으로 이동
		if(result) {	// true라면 조회(list) Mapping으로 이동
			return "redirect:list";
		}
		else {	// false라면 삭제 실패 Mapping으로 이동
			return "music/editFail";
		}
	}

	// 2. 삭제 실패 Mapping
	@GetMapping("/delete_fail")
	public String deleteFail() {
		// 삭제 실패 페이지(deleteFail.jsp)으로 이동
		return "music/deleteFail";
	}
}

 


 

회원 관련 기능 

- 회원 가입 : day29 - 0902

 

- MEMBER 테이블 생성

 - 회원 아이디 : member_id, 5 ~ 20자 영문 소문자, 숫자와 특수기호(_), (-)만 사용 가능
 - 회원 비밀번호 : member_pw, 8~16자 영문 대 소문자, 숫자, 특수문자(!, @, #, $)를 각각 1개 이상 사용
 - 닉네임 : member_nick, 한글로 시작하며 한글 or 숫자 가능, 총 10자 이내
 - 생년월일 : member_birth, YYYY-MM-DD, 날짜 형식
 - 전화번호 : member_tel, 대시 제외하고 010XXXXXXXX 형태
 - 이메일 : member_email, 100byte 이내, @ 반드시 포함
 - 주소
    - 우편 주소 : member_post, 5 ~ 6자리
    - 기본 주소 : member_base_address, 한글 50자
    - 상세 주소 : member_detail_address, 한글 50자
 - 포인트 : member_point, 0 이상 설정
 - 등급 : member_grade, 일반, VIP, 관리자 중 하나
 - 가입일시 : member_join, 날짜이며 현재 시간으로 자동 설정
 - 접속일시 : member_login, 로그인 할 때의 시간으로 자동 설정
 ** 회원 아이디가 기본키
 ** 전화번호, 이메일, 주소는 선택 입력 가능
 ** 접속일시는 가입시 설정하지 않음

 

-- 테이블 생성
create table member(
member_id varchar2(20) primary key
check(regexp_like(member_id, '^[a-z0-9-_]{5,20}$')),
member_pw varchar2(16) not null check(
					regexp_like(member_pw, '^[a-zA-Z0-9!@#$]{8,16}$')
					and
					regexp_like(member_pw, '[a-z]')
					and
					regexp_like(member_pw, '[A-Z]')
					and
					regexp_like(member_pw, '[0-9]')
					and
					regexp_like(member_pw, '[!@#$]')
					),
member_nick varchar2(30) not null unique check(regexp_like(member_nick, '^[가-힣][가-힣0-9]{0,9}$')),
member_birth date not null,
member_tel char(11) check(regexp_like(member_tel, '^010[0-9]{8}$')),
member_email varchar2(100) check(regexp_like(member_email, '@')),
member_post varchar2(6) check(regexp_like(member_post, '^[0-9]{5,6}$')),
member_base_address varchar2(150),
member_detail_address varchar2(150),
member_point number default 0 not null check(member_point >= 0),
member_grade varchar2(9) default '일반' not null check(member_grade in ('일반', 'VIP', '관리자')),
member_join date default sysdate not null,
member_login date
);

 

 

준비 - Model이 될 클래스 정의 (DTO 또는 VO)

package com.kh.springhome.entity;

import java.sql.Date;
 
public class MemberDto {

	// 필드
	private String memberId;
	private String memberPw;
	private String memberNick;
	private Date memberBirth;
	private String memberTel;
	private String memberEmail;
	private String memberPost;
	private String memberBaseAddress;
	private String memberDetailAddress;
	private int memberPoint;
	private String memberGrade;
	private Date memberJoin;
	private Date memberLogin;
	
	// 생성자
	public MemberDto() {
		super();
	}
	
	// getter & setter
	public String getMemberId() {
		return memberId;
	}
	public void setMemberId(String memberId) {
		this.memberId = memberId;
	}
	public String getMemberPw() {
		return memberPw;
	}
	public void setMemberPw(String memberPw) {
		this.memberPw = memberPw;
	}
	public String getMemberNick() {
		return memberNick;
	}
	public void setMemberNick(String memberNick) {
		this.memberNick = memberNick;
	}
	public Date getMemberBirth() {
		return memberBirth;
	}
	public void setMemberBirth(Date memberBirth) {
		this.memberBirth = memberBirth;
	}
	public String getMemberTel() {
		return memberTel;
	}
	public void setMemberTel(String memberTel) {
		this.memberTel = memberTel;
	}
	public String getMemberEmail() {
		return memberEmail;
	}
	public void setMemberEmail(String memberEmail) {
		this.memberEmail = memberEmail;
	}
	public String getMemberPost() {
		return memberPost;
	}
	public void setMemberPost(String memberPost) {
		this.memberPost = memberPost;
	}
	public String getMemberBaseAddress() {
		return memberBaseAddress;
	}
	public void setMemberBaseAddress(String memberBaseAddress) {
		this.memberBaseAddress = memberBaseAddress;
	}
	public String getMemberDetailAddress() {
		return memberDetailAddress;
	}
	public void setMemberDetailAddress(String memberDetailAddress) {
		this.memberDetailAddress = memberDetailAddress;
	}
	public int getMemberPoint() {
		return memberPoint;
	}
	public void setMemberPoint(int memberPoint) {
		this.memberPoint = memberPoint;
	}
	public String getMemberGrade() {
		return memberGrade;
	}
	public void setMemberGrade(String memberGrade) {
		this.memberGrade = memberGrade;
	}
	public Date getMemberJoin() {
		return memberJoin;
	}
	public void setMemberJoin(Date memberJoin) {
		this.memberJoin = memberJoin;
	}
	public Date getMemberLogin() {
		return memberLogin;
	}
	public void setMemberLogin(Date memberLogin) {
		this.memberLogin = memberLogin;
	}

	// toString 오버라이딩 - memberPw(비밀번호)는 보안을 위해 제외할 것
	@Override
	public String toString() {
		return "MemberDto [memberId=" + memberId 
 					+ ", memberNick=" + memberNick
 					+ ", memberBirth=" + memberBirth 
 					+ ", memberTel=" + memberTel 
 					+ ", memberEmail=" + memberEmail
 					+ ", memberPost=" + memberPost 
 					+ ", memberBaseAddress=" + memberBaseAddress 
 					+ ", memberDetailAddress=" + memberDetailAddress 
 					+ ", memberPoint=" + memberPoint 
 					+ ", memberGrade=" + memberGrade
 					+ ", memberJoin=" + memberJoin 
 					+ ", memberLogin=" + memberLogin + "]";
	}
}

 

회원 목록

MemberDao

public interface MemberDao {

	// 추상 메소드 - 회원 목록
	// 1) 전체 조회
	List<MemberDto> selectList();
	// 2) 검색 조회
	List<MemberDto> selectList(String type, String keyword);
}

 

MemberDaoImpl

@Repository
public class MemberDaoImpl implements MemberDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;

	// MemberDto에 대한 RowMapper
	private RowMapper<MemberDto> mapper = new RowMapper<>() {
		@Override
		public MemberDto mapRow(ResultSet rs, int rowNum) throws SQLException {
			MemberDto memberDto = new MemberDto();
			memberDto.setMemberId(rs.getString("member_id"));
			memberDto.setMemberPw(rs.getString("member_pw"));
			memberDto.setMemberNick(rs.getString("member_nick"));
			memberDto.setMemberBirth(rs.getDate("member_birth"));
			memberDto.setMemberTel(rs.getString("member_tel"));
			memberDto.setMemberEmail(rs.getString("member_email"));
			memberDto.setMemberPost(rs.getString("member_post"));
			memberDto.setMemberBaseAddress(rs.getString("member_base_address"));
			memberDto.setMemberDetailAddress(rs.getString("member_detail_address"));
			memberDto.setMemberPoint(rs.getInt("member_point"));
			memberDto.setMemberGrade(rs.getString("member_grade"));
			memberDto.setMemberJoin(rs.getDate("member_join"));
			memberDto.setMemberLogin(rs.getDate("member_login"));
			return memberDto;
		}
	};
	
	// 추상 메소드 - 회원 목록
	// 1) 전체 조회
	@Override
	public List<MemberDto> selectList() {
		String sql = "select * from member order by member_id asc";
		return jdbcTemplate.query(sql, mapper);
	}
	// 2) 검색 조회
	@Override
	public List<MemberDto> selectList(String type, String keyword) {
		String sql = "select * from member where instr(#1, ?) > 0 order by #1 asc";
		sql = sql.replace("#1", type);
		Object[] param = new Object[] {keyword};
		return jdbcTemplate.query(sql, mapper, param);
	}
}

 

MemberController

@Controller
@RequestMapping("/member")
public class MemberController {

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;
	
	// 2. 회원 목록 - isSearch가 true일 경우 검색 조회, false일 경우 전체 조회
	// 회원 목록 Mapping
	@GetMapping("/list")
	public String list(Model model, 
							@RequestParam(required = false) String type, 
							@RequestParam(required = false) String keyword) {
		// 조회 유형 판정 - 유형(type)과 검색어(keyword)가 null이 아니면 검색 목록(isSearch가 true)
		boolean isSearch = type != null && keyword != null;
		// model에 조회 결과 첨부
		if(isSearch) {	// true일 경우 검색 조회 selectList(String type, String keyword)의 결과 첨부
			model.addAttribute("list", memberDao.selectList(type, keyword));
		}
		else {	// false일 경우 전체 조회 selectList()의 결과 첨부
			model.addAttribute("list", memberDao.selectList());
		}
		// 회원 목록 페이지(list.jsp)로 연결
		// 	return "/WEB-INF/views/member/list.jsp";
		return "member/list";
	}
}

 

list.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<html>
<head>
<meta charset="UTF-8">
<title>회원 목록</title>
</head>
<body>

<div align = "center">

<h1>회원 목록</h1>

<%-- 검색창 --%>
<form action = "list" method = "get" >

	<c:choose>
		<%-- <select>의 이름(name)인 type을 변수화하여 검색 후에도 해당 선택이 그대로 유지되도록 --%>
		<%-- type의 값이 member_nick일 때 <select>의 <option>이 자동으로 member_nick이 선택(selected)되어 있도록--%>
		<c:when test = "${param.type == 'member_nick'}">
			<select name = "type" required>
				<option value = "member_id">아이디</option>
				<option value = "member_nick" selected>닉네임</option>
				<option value = "member_tel">전화번호</option>
				<option value = "member_email">이메일</option>
				<option value = "member_grade">등급</option>
			</select>
		</c:when>	
		<%-- type의 값이 member_tel일 때 <select>의 <option>이 자동으로 member_tel이 선택(selected)되어 있도록--%>
		<c:when test = "${param.type == 'member_tel'}">
			<select name = "type" required>
				<option value = "member_id">아이디</option>
				<option value = "member_nick">닉네임</option>
				<option value = "member_tel" selected>전화번호</option>
				<option value = "member_email">이메일</option>
				<option value = "member_grade">등급</option>
			</select>
		</c:when>
		<%-- type의 값이 member_email일 때 <select>의 <option>이 자동으로 member_email이 선택(selected)되어 있도록--%>
		<c:when test = "${param.type == 'member_email'}">
			<select name = "type" required>
				<option value = "member_id">아이디</option>
				<option value = "member_nick">닉네임</option>
				<option value = "member_tel">전화번호</option>
				<option value = "member_email" selected>이메일</option>
				<option value = "member_grade">등급</option>
			</select>
		</c:when>
		<%-- type의 값이 member_grade일 때 <select>의 <option>이 자동으로 member_grade이 선택(selected)되어 있도록--%>
		<c:when test = "${param.type == 'member_grade'}">
			<select name = "type" required>
				<option value = "member_id">아이디</option>
				<option value = "member_nick">닉네임</option>
				<option value = "member_tel">전화번호</option>
				<option value = "member_email">이메일</option>
				<option value = "member_grade" selected>등급</option>
			</select>
		</c:when>
		<%-- type의 값이 member_id일 때 <select>의 <option>이 자동으로 member_id가 선택(selected)되어 있도록--%>
		<c:otherwise>
			<select name = "type" required>
				<option value = "member_id" selected>아이디</option>
				<option value = "member_nick">닉네임</option>
				<option value = "member_tel">전화번호</option>
				<option value = "member_email">이메일</option>
				<option value = "member_grade">등급</option>
			</select>
		</c:otherwise>
	</c:choose>

	<%-- <input>의 이름(name)인 keyword을 변수화하여 검색 후에도 해당 검색어가 그대로 남아있도록 --%>
	<input name = "keyword" type = "search" placeholder = "검색어" value = "${param.keyword}" required>
	<button type = "submit">검색</button>
</form>

<br>

<%-- 회원 목록 표시 - 기본적으로는 전체 조회가 표시되며 검색 조회시 그 결과가 표시되도록 --%>
<table border = "1" width = 900>
	<thead>
		<tr>
			<th>회원 아이디</th>
			<th>닉네임</th>
			<th>생년월일</th>
			<th>전화번호</th>
			<th>이메일</th>
			<th>등급</th>
			<th>상세정보</th>
		</tr>
	</thead>
		
	<tbody align = "center">
		<c:forEach var = "memberDto" items = "${list}">
			<tr>
				<td>${memberDto.getMemberId()}</td>
				<td>${memberDto.getMemberNick()}</td>
				<td>${memberDto.getMemberBirth()}</td>
				<td>${memberDto.getMemberTel()}</td>
				<td>${memberDto.getMemberEmail()}</td>
				<td>${memberDto.getMemberGrade()}</td>
				<td>
					<%-- 각 회원의 ID를 RequestParam으로 하여 상세, 수정, 탈퇴가 되도록 --%>
					<a href = "detail?memberId=${memberDto.getMemberId()}">상세</a>
					<a href = "edit?memberId=${memberDto.getMemberId()}">수정</a>
					<a href = "delete?memberId=${memberDto.getMemberId()}">탈퇴</a>
				</td>
			</tr>
		</c:forEach>
	</tbody>
	
	<tfoot>
		<tr>
			<%-- 회원 목록의 총 수를 표시하기 위해 list의 총 길이를 반환하는 .size() 사용 --%>
			<td colspan = "7" align = "right">총 ${list.size()}개의 결과</td>
		</tr>
	</tfoot>
</table>

<h2><a href = "/">메인 페이지로 돌아가기</a></h2>
</div>

</body>
</html>

 

- 검색창

아이디, 닉네임, 전화번호, 등급 중에서 검색할 카테고리(type)를 선택한 후 검색하면

검색 조회 후에도 그 카테고리 선택이 그대로 남아있도록 하기 위해서는  <option>에서 selected 속성을 사용한다

<c:choose> 안에 <c:when>, <c:otherwise>와 test 속성을 이용하여 경우를 나누고 각 경우에 대하여 selected 속성을 적용

 

- 상세, 수정, 탈퇴

하이퍼링크에 RequestParam을 미리 입력하는 방식으로 해당 memberId를 가지고 있는 회원에 대해

상세정보, 정보수정, 회원탈퇴가 되도록 할 수 있다

 

- 회원 목록 또는 검색 조회 결과의 총 수

Controller에서 List<MemberDto>의 형태로 전달받은 list의 크기를 반환하는 .size()로 조회 결과의 총 수를 반환할 수 있다

 

회원 상세 정보

MemberDao

public interface MemberDao {
 
	// 추상 메소드 - 회원 상세 정보
	MemberDto selectOne(String memberId);
}

 

MemberDaoImpl

@Repository
public class MemberDaoImpl implements MemberDao {

	// 의존성 주입
	@Autowired
	private JdbcTemplate jdbcTemplate;

	// MemberDto에 대한 ResultSetExtractor
	private ResultSetExtractor<MemberDto> extractor = new ResultSetExtractor<>() {
		@Override
		public MemberDto extractData(ResultSet rs) throws SQLException, DataAccessException {
			if(rs.next()) {
				MemberDto memberDto = new MemberDto();
				memberDto.setMemberId(rs.getString("member_id"));
				memberDto.setMemberPw(rs.getString("member_pw"));
				memberDto.setMemberNick(rs.getString("member_nick"));
				memberDto.setMemberBirth(rs.getDate("member_birth"));
				memberDto.setMemberTel(rs.getString("member_tel"));
				memberDto.setMemberEmail(rs.getString("member_email"));
				memberDto.setMemberPost(rs.getString("member_post"));
				memberDto.setMemberBaseAddress(rs.getString("member_base_address"));
				memberDto.setMemberDetailAddress(rs.getString("member_detail_address"));
				memberDto.setMemberPoint(rs.getInt("member_point"));
				memberDto.setMemberGrade(rs.getString("member_grade"));
				memberDto.setMemberJoin(rs.getDate("member_join"));
				memberDto.setMemberLogin(rs.getDate("member_login"));
				return memberDto;
			}
			else {
				return null;
			}
		}
	};

	// 추상 메소드 오버라이딩 - 회원 상세 정보
	@Override
	public MemberDto selectOne(String memberId) {
		String sql = "select * from member where member_id = ? order by member_join asc";
		Object[] param = new Object[] {memberId};
		return jdbcTemplate.query(sql, extractor, param);
	}
}

 

MemberController

@Controller
@RequestMapping("/member")
public class MemberController {

	// 의존성 주입
	@Autowired
	private MemberDao memberDao;

	// 3. 회원 상세 정보
	// 회원 상세 정보 Mapping
	@GetMapping("/detail")
	public String detail(Model model, @RequestParam String memberId) {
		// model에 단일 조회 selectOne(String memberId)의 결과를 첨부
		model.addAttribute("memberDto", memberDao.selectOne(memberId));
		// 회원 상세 페이지(detail.jsp)로 연결
		// return "/WEB-INF/views/member/detail.jsp";
		return "member/detail";
	}
}

 

detail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

<html>
<head>
<meta charset="UTF-8">
<title>회원 상세 정보</title>
</head>
<body>

<div align = "center">
	
<h1>${memberDto.getMemberId()} 회원 상세 정보</h1>

<table border = "1" width = 400>
<tbody>
	<tr>
		<th width = "25%">아이디</th>
		<td>${memberDto.getMemberId()}</td>
	</tr>
	<tr>
		<th>닉네임</th>
		<td>${memberDto.getMemberNick()}</td>
	</tr>
	<tr>
		<th>생년월일</th>
		<td>${memberDto.getMemberBirth()}</td>
	</tr>
	<tr>
		<th>전화번호</th>
		<td>${memberDto.getMemberTel()}</td>
	</tr>
	<tr>
		<th>이메일</th>
		<td>${memberDto.getMemberEmail()}</td>
	</tr>
	<tr>
		<th>주소</th>
		<td>
			<c:if test="${memberDto.getMemberPost() != null}">
				[${memberDto.getMemberPost()}]
				${memberDto.getMemberBaseAddress()}
				${memberDto.getMemberDetailAddress()}
			</c:if>					
		</td>
	</tr>
	<tr>
		<th>포인트</th>
		<td>
			<fmt:formatNumber pattern = "#,##0">${memberDto.getMemberPoint()}</fmt:formatNumber>
		</td>
	</tr>
	<tr>
		<th>등급</th>
		<td>${memberDto.getMemberGrade()}</td>
	</tr>
	<tr>
		<th>가입일</th>
		<td>
			<fmt:formatDate value = "${memberDto.getMemberJoin()}" pattern = "y년 M월 d일 E a h시 m분 s초"/>
		</td>
	</tr>
	<tr>
		<th>마지막 로그인</th>
		<td>
			<fmt:formatDate value = "${memberDto.getMemberLogin()}" pattern = "y년 M월 d일 E a h시 m분 s초"/>
		</td>
	</tr>
</tbody>
</table>

<h3><a href = "edit?memberId=${memberDto.getMemberId()}">회원 정보 수정</a></h3>

<h3><a href = "delete?memberId=${memberDto.getMemberId()}">회원 탈퇴</a></h3>
	
</div>

</body>
</html>

- 주소 관련 항목 출력

주소와 관련된 항목(member_post, member_base_address, member_detail_address)는 모두 선택사항이므로 

만약 우편 주소(member_post)가 null이 아니라면 주소와 관련된 항목이 출력되도록 한다

 

- 포인트(member_point) 표기 형식

포인트는 1000 단위로 컴마(,)를 찍어서 표기하기 위해 <fmt:formatNumber>의 pattern 속성의 값을 "#,##0"로 한다

 

- 가입일(member_join)과 마지막 로그인(member_login)의 표시 형식

년(y), 월(M), 일(d), 요일(E), 오전/오후(a), 시(h), 분(m), 초(s)가 모두 표시되도록 하기 위해

<fmt:formDate>의 pattern 속성의 값을 "y년 M월 d일 E a h시 m분 s초"로 한다

 

- 회원 정보 변경, 회원 탈퇴

하이퍼링크에 RequestParam을 미리 입력하는 방식으로 해당 memberId를 가지고 있는 회원에 대해

회원 정보 수정, 회원 탈퇴가 되도록 할 수 있다