day32 - 0907
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를 가지고 있는 회원에 대해
회원 정보 수정, 회원 탈퇴가 되도록 할 수 있다