물품 상세페이지에서 주문하기 버튼 클릭시
<form th:action="@{/customer/order.do}" method="post">
로 이동
물품 번호<input type="hidden" name="itemno" th:value="${item.no}" />
와
수량 <select name="cnt">
을 content.html
에서 전송
사용자 아이디는content.html
에서 전송하는게 아니라 Controller @AuthenticationPrincipal User
세션에서 로그인된 사용자 아이디 정보 꺼내서 사용
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<a th:href="@{/admin/home.do}"><button>관리자 홈</button></a>
<a th:href="@{/seller/home.do}"><button>판매자 홈</button></a>
<a th:href="@{/customer/home.do}"><button>고객 홈</button></a>
<hr />
<h3>물품상세</h3>
<form th:action="@{/customer/order.do}" method="post">
<!-- homecontroller 에서 item으로 넘겨줬었다! -->
<!-- 물품정보는 1개이니 반복문 사용하지 않음 -->
<!-- 컨트롤러로 아이템 번호 전송하기 -->
<input type="hidden" name="itemno" th:value="${item.no}" />
<p th:text="${item.no}"></p>
<p th:text="${item.price}"></p>
<p th:text="${item.content}"></p>
<p th:text="|${item.price} 원|"></p>
<!-- 이미지는 여러개이니 반복문 사용 -->
<!-- 이미지가 비어있지 않은경우 -->
<div th:if="${not #lists.isEmpty(list)}">
<th:block th:each="item, idx : ${list}">
<img th:src="@{/item_image(no=${item.no})}" style="width:200px;" />
</th:block>
</div>
<!-- 이미지가 비어있는경우 -->
<div th:if="${#lists.isEmpty(list)}">
<img th:src="@{/item_image(no=0)}" style="width:200px;" />
</div>
<!-- 컨트롤러로 수량 cnt 전송하기 -->
<select name="cnt">
<th:block th:each="tmp, idx : ${#numbers.sequence(1,100)}">
<option th:text="${tmp}" th:value="${tmp}"></option>
</th:block>
</select>
<input type="submit" value="주문하기" />
</form>
</body>
</html>
CREATE SEQUENCE SEQ_ORDER_NO START WITH 1 INCREMENT BY 1 NOMAXVALUE NOCACHE;
CREATE TABLE ORDERTBL(
NO NUMBER CONSTRAINT PK_ORDER_NO PRIMARY KEY,
CNT NUMBER,
ITEMNO NUMBER CONSTRAINT FK_ITEMTBL_NO REFERENCES ITEMTBL(NO),
USERID VARCHAR2(30) CONSTRAINT FK_MEMBERTBL_USERID REFERENCES MEMBERTBL(USERID),
REGDATE TIMESTAMP DEFAULT CURRENT_DATE
);
DTO생성
@Getter
@Setter
@ToString
@NoArgsConstructor
public class OredeDTO {
long no;
long cnt;
long itemno;
String userid;
Date regdate;
}
- 컨트롤러 생성시 어떤 데이터를 파라미터로 넘겨 받을지 고민해야한다
- ORDERTBL에서 사용자 아이디를 확보하기 위해,
주문전에 사용자가 로그인된 상태에서 주문 가능하게 만들었어야 한다
➡️ 그래야 주문시 세션에서 사용자 id확보 가능
- form을 통해서 물품번호/수량 = 2개의 전달값 받기
- 세션에서 로그인한 사용자 id확보
// 주문하기
@PostMapping(value = "/order.do")
public String OrderPOST(
@ModelAttribute OrderDTO order,
@AuthenticationPrincipal User user) {
order.setUserid(user.getUsername());
omapper.insertOrder(order);
return "redirect:/customer/orderlist.do";
}
Mapper 주문등록 생성
➡️ insert니까 int로 반환
@Mapper
public interface OrderMapper {
// 주문등록
public int insertOrder (OrderDTO order);
}
주문 등록하기
<insert id="insertOrder" parameterType="com.example.dto.OrderDTO">
INSERT INTO ORDERTBL(NO, CNT, ITEMNO, USERID, REGDATE)
VALUES(SEQ_ORDER_NO.NEXTVAL, #{cnt}, #{itemno}, #{userid}, CURRENT_DATE)
</insert>
주문조회시 상품의 정보, 판매자의 정보 등 추가적인 정보가 부족
➡️ 추가적으로 조회결과에 포함시킬 테이블을 JOIN하여 하나의 가상의 테이블을 생성해준다
💡 ITEMTBL
, ORDERTBL
, MEMBERTBL
의 필요한 데이터들만 가져와 JOIN하여 조회만 가능한 가상의 테이블인 VIEW
생성
ORDERDTO
를 보내면 고객이 주문내역을 확인하기에는 데이터가 부족하다ITEMTBL
과 ORDERTBL
JOINSELECT
T1.* ,T2.*
FROM ITEMTBL T1
INNER JOIN
ORDERTBL T2
ON T1.NO = T2.ITEMNO
🖨️ 결과
조회시 중복되는 데이터는 하나만 가져오고,
불필요한 데이터는 제외하여 조건설정 후 재조회
사진
중복된 데이터 ➡️ T1.NO = T2.ITEMNO
, T1.REGDATE = T2.REGDATE
불필요한 데이터 ➡️ QUANTITY
필요 없으니 제외
SELECT
T1.NAME, T1.CONTENT, T1.PRICE, T1.SELLER, T2.*
FROM ITEMTBL T1
INNER JOIN
ORDERTBL T2
ON T1.NO = T2.ITEMNO
🖨️ 결과
위의 조회값에 MEMBERTBL
의 아이디, 비밀번호, 등록일, 권한 제외한 나머지 데이터만 추가로 JOIN 하여 VIEW 생성
CREATE OR REPLACE VIEW ORDER_VIEW AS
SELECT
T1.AGE, T1.PHONE, T1.GENDER, T2.*
FROM
MEMBERTBL T1
INNER JOIN
(
SELECT
T1.NAME, T1.CONTENT, T1.PRICE, T1.SELLER, T2.*
FROM
ITEMTBL T1
INNER JOIN
ORDERTBL T2
ON
T1.NO=T2.ITEMNO
) T2
ON
T1.USERID= T2.USERID;
🖨️ 결과
SELECT O.* FROM ORDER_VIEW O;
🖨️ 결과
고객용 주문내역 생성
Mapper에서 조회된 데이터를 보낼때
DTO
또는MAP
을 사용한다
Map<String, Object>
= 변수명(key)과 type 정의 되어있지 않음DTO
= 변수명(key)과 type 정의 되어있음
➡️DTO
로 데이터 보내고 싶은 경우, 별도의 주문내역 조회용DTO
를 따로 생성하여DTO
타입의 데이터를 전송한다
return값이 map
인 경우 주문내역 조회시 데이터가 원하는 출력형태로 나오지 않는 경우
➡️ resultmap
을 사용하여 원하는 출력형태로 형변환을 해주어야 한다
DTO와 달리 map은 형태가 지정되어있지 않기 때문에 출력시 형변환이 필요한 데이터가 있다
+사진
<!-- 고객 주문내역 조회 -->
<resultMap id="retMap1" type="map">
<result property="CONTENT" column="CONTENT" jdbcType="VARCHAR" javaType="String" />
<result property="ITEMNO" column="ITEMNO" jdbcType="NUMERIC" javaType="Long" />
</resultMap>
<select id="selectOrderList" parameterType="String"
resultMap="retMap1">
SELECT O.* FROM ORDER_VIEW O WHERE USERID=#{userid}
</select>
System.out.println(map.toString());
map
을 출력해보면 컬럼명이 대문자인것을 확인할 수 있다
사진
map
은 형태와 변수명이 지정되어 있지 않기 때문이며,
orderMapper.xml
에서 컬럼명을 대문자로 설정해 주었기 때문에 나머지 컬럼들도 대문자로 자동지정 된다
➡️ 변수명을 바꾸고 싶은 경우 orderMapper.xml
에서 원하는 컬럼명을 지정해주면 변경 가능
// 고객 주문목록 확인
@GetMapping(value = "/orderlist.do")
public String orderListGET(
@AuthenticationPrincipal User user,
Model model) {
// 주문내역 목록
List<Map<String, Object>> map = omapper.selectOrderList(user.getUsername());
// 출력하여 데이터 넘어오는지 확인하고 mapper진행하기!
System.out.println(map.toString());
// 주문내역을 반복한 후에 대표이미지를 가져와서 map에 포함
for (Map<String, Object> tmp : map) {
// 물품번호 (dto사용시 => dto.getItemno())
Long itemno = (Long) tmp.get("ITEMNO");
// 물품번호 전달 후 대표 이미지 1개의 이미지 번호 반환
ItemImageDTO obj = iimapper.selectImageNoOne(itemno);
// map에 항목을 key가 IMAGE인 항목을 추가함 (dto사용시 => dto.setImage(url))
// map은 put으로 데이터 넣어줌
tmp.put("IMAGE", "/item_image?no=" + obj.getNo());
}
// 데이터 확인 후 model에 넣어주기
model.addAttribute("list", map);
return "customer/orderlist";
}
...
<!-- ORDER_VIEW -->
<table border="1">
<tr>
<th>주문번호</th>
<th>주문수량</th>
<th>주문일자</th>
<th>물품번호</th>
<th>물품명</th>
<th>물품내용</th>
<th>물품가격</th>
<th>판매자아이디</th>
<th>주문자아이디</th>
<th>주문자나이</th>
<th>주문자연락처</th>
<th>주문자성별</th>
</tr>
<tr th:each="obj, idx : ${list}">
<td th:text="${obj.NO}"></td>
<td th:text="${obj.CNT}"></td>
<td th:text="${obj.REGDATE}"></td>
<td th:text="${obj.ITEMNO}"></td>
<td th:text="${obj.NAME}"></td>
<td th:text="${obj.CONTENT}"></td>
<td th:text="${obj.PRICE}"></td>
<td th:text="${obj.SELLER}"></td>
<td th:text="${obj.USERID}"></td>
<td th:text="${obj.AGE}"></td>
<td th:text="${obj.PHONE}"></td>
<td th:text="${obj.GENDER}"></td>
</tr>
</table>
...
기존의
selectImageNoOne
을 이용해 대표이미지 1개 가져온 후 주문내역을 반복문으로 반복하며 map에 포함해준다
// 주문내역을 반복한 후에 대표이미지를 가져와서 map에 포함
for (Map<String, Object> tmp : map) {
// 물품번호 (dto사용시 => dto.getItemno())
Long itemno = (Long) tmp.get("ITEMNO");
// 물품번호 전달 후 대표 이미지 1개의 이미지 번호 반환
ItemImageDTO obj = iimapper.selectImageNoOne(itemno);
// map에 항목을 key가 IMAGE인 항목을 추가함 (dto사용시 => dto.setImage(url))
// map은 put으로 데이터 넣어줌
tmp.put("IMAGE", "/item_image?no=" + obj.getNo());
}
<th>이미지</th>
<td>
<img th:src="@{ ${obj.IMAGE} }" style="width: 50px; height:50px" />
</td>
체크박스로 주문취소하기
form
태그 생성
➡️th:action="@{/customer/orderdelete.do}" method="post"
checkbox
생성후 넘겨줄 이름name="chk"
, 값th:value="${obj.NO}"
지정
<!-- 스트립트에게 알려줄 id="form" -->
<form th:action="@{/customer/orderdelete.do}" method="post" id="form">
<table border="1">
<tr>
<th>체크</th>
...
<td><input type="checkbox" name="chk" th:value="${obj.NO}" /></td>
html에서 넘겨준 여러개의 주문번호를
배열
, 혹은list
둘중 한가지의 방법으로 받는다
@RequestParam(name = "chk") long[] chk){
@RequestParam(name = "chk") List<Long> chk){
// 고객 주문취소
@PostMapping(value = "/orderdelete.do")
public String orderDelete(
// 배열사용
@RequestParam(name = "chk") Long[] chk) {
omapper.deleteOrder(chk);
return "redirect:/customer/orderlist.do";
}
배열로 받아온 주문번호를 넘겨주면 삭제결과를 int로 반환
// 고객주문 주문취소
public int deleteOrder ( Long[] no );
여러개의 주문번호를 배열로 받아와 삭제시 두가지 방법을 사용할 수 있다
➡️ 좀더 간결한 방법2 사용
- 방법1
<delete id="deleteOrder" parameterType="list"> DELETE FROM ORDERTBL WHERE NO IN( <foreach collection="no" item="tmp" separator=","> #{tmp} </foreach> </delete>
- 방법2
<delete id="deleteOrder" parameterType="list"> <foreach collection="no" item="tmp" separator=", " open="DELETE FROM ORDERTBL WHERE NO IN( " close=") " > #{tmp} </foreach> </delete>
<!-- 주문일괄삭제 -->
<delete id="deleteOrder" parameterType="list">
<!-- DELETE FROM ORDERTBL WHERE NO 여러개 삭제시 => IN(1,2,3,4,5) -->
<!-- IN에 들어갈 숫자배열은 길이가 정해지지 않은 개수의 숫자가 들어오니까 IN 내부에서 반복문실행 -->
<!-- IN(1,2,3,4,5) separator(구분자)는 "," -->
<foreach collection="no" item="tmp" separator=", "
open="DELETE FROM ORDERTBL WHERE NO IN( " close=") " >
#{tmp}
</foreach>
</delete>
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>고객 홈화면</title>
</head>
<body>
<h3>고객 홈화면</h3>
<a th:href="@{/}"><button>홈으로</button></a>
<hr /><br />
<!-- 나이, 연락처, 성별 -->
<a th:href="@{/customer/update.do}"><button>정보수정</button></a>
<a th:href="@{/customer/updatepw.do}"><button>암호변경</button></a>
<a th:href="@{/customer/delete.do}"><button>회원탈퇴</button></a>
<a th:href="@{/customer/orderlist.do}"><button>주문목록</button></a>
</body>
</html>
회원정보 수정페이지 생성
submit
누르면 컨트롤러로 이동
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>회원정보수정</title>
</head>
<body>
<form th:action="@{/customer/updateinfo.do}" method="post">
아이디 : <input type="text" name="userid" th:value="${obj.userid}" /><br />
나이 : <input type="number" name="age" th:value="${obj.age}" /><br />
연락처 : <input type="text" name="phone" th:value="${obj.phone}" /><br />
성별 :
<select name="gender" >
<option th:value="M" th:text="남" th:selected="${obj.gender == 'M'}" />
<option th:value="F" th:text="여" th:selected="${obj.gender == 'F'}" />
</select>
<br />
<input type="submit" value="변경하기" /><br />
</form>
</body>
</html>
회원정보 수정페이지 이동시 기존 회원정보 데이터 불러오기
// 회원정보수정을 위한 회원정보 조회
@GetMapping(value = "/update.do")
public String updateGET(
@AuthenticationPrincipal User user,
Model model
){
// 로그인 정보를 읽어와서 아이디, 나이 , 연락처, 성별 조회
MemberDTO member = mmapper.selectMemberOne(user.getUsername());
model.addAttribute("obj", member);
return "customer/update";
}
Controller
에서 반환값 int로 지정
➡️ 조회 제외한 나머지 수정, 삭제 등록은 자동 int반환이라 생각하면 됨// 회원정보 수정하기 public int updateinfoMember( MemberDTO member );
<!-- 회원정보 수정하기 -->
<update id="updateinfoMember" parameterType="com.example.dto.MemberDTO">
UPDATE MEMBERTBL SET
AGE=#{age}, PHONE=#{phone}, GENDER=#{gender}
WHERE
USERID=#{userid}
</update>
회원정보 수정 완료시 return값
"alert";
로 이동
// 회원정보 수정하기
@PostMapping(value = "/updateinfo.do")
public String updateGET(
HttpServletRequest request,
@ModelAttribute MemberDTO member,
Model model
){
int ret = mmapper.updateinfoMember(member);
if(ret == 1){ //수정 성공인 경우
model.addAttribute("msg", "회원정보가 변경됨");
model.addAttribute("url", request.getContextPath() + "/customer/home.do");
} else{ //수정 실패인 경우
model.addAttribute("msg", "회원정보가 변경됨");
model.addAttribute("url", request.getContextPath() + "/customer/update.do");
}
// get으로 되어있는 주소정보 변경
return "alert";
}
CustomerController
에서model
에 담아 보내준msg
와url
을script
로 출력
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
const msg = "[[${msg}]]";
const url = "[[${url}]]";
alert(msg);
window.location.href=url;
</script>
</head>
</html>
암호변경페이지로 이동
@GetMapping(value="/updatepw.do")
public String updatepwGET() {
return "customer/updatepw";
}
암호변경시 입력값 전달을 위해
DTO
를 만들수도 있지만map
을 만들어 사용해도 된다
➡️updatepw.html
에서 지정한name
값들을Controller
에서map
으로 받는다
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>암호변경</title>
</head>
<body>
<form th:action="@{/customer/updatepw.do}" method="post">
기존 암호 : <input type="password" name="userpw" /><br />
변경 암호 : <input type="password" name="userpw1" /><br />
변경 암호 확인 : <input type="password" name="userpw2" /><br />
<br />
<input type="submit" value="변경하기" /><br />
</form>
</body>
</html>
입력된 기존암호와 변경할 새 암호 받아와서 DB의 사용자 암호 변경하기
➡️ 기존 암호를 가져와서 변경하는 경우 hash값이 계속 변경되기 때문에 암호 비교 불가능
Map
을 새로 생성하여 기존 아이디와 바꿀 암호값을 넣어준다mmapper.updateMemberPw
를 이용하여,update
시켜준다 @PostMapping(value="/updatepw.do")
public String updatepwPOST(
@AuthenticationPrincipal User user,
@RequestParam Map<String, Object> map
) {
System.out.println(map.toString());
// 1. 로그인 된 아이디를 이용해서 기존 아이디, 암호를 가져옴
MemberDTO member = mmapper.selectMemberOne(user.getUsername());
String memberid = member.getUserid();
String memberpw = member.getUserpw();
// 2. 기존 DB의 암호와 현재 암호를 비교하는게 필요
// BCryptPasswordEncoder =>회원가입때 사용했었음 ,암호화
// 기존db암호 = member.getUserpw
// 현재암호 = rowpw
String rowpw = (String) map.get("userpw");
String rowpw1 = (String) map.get("userpw1");
BCryptPasswordEncoder bcpe = new BCryptPasswordEncoder();
// 3. 암호변경 수행
// bcpe.matches("rawPassword = 현재암호", "encodedPassword = DB암호")
// if문 통과하면 암호가 일치
if(bcpe.matches( rowpw , memberpw )){//일치하는경우
// 암호변경
Map<String, Object> map1 = new HashMap<>();
map1.put("userid", memberid);
map1.put("userpw", bcpe.encode(rowpw1));
mmapper.updateMemberPw(map1);
} else {//일치하지 않는경우
return "customer/updatepw";
}
return "redirect:/customer/home.do";
}
// 암호변경
public int updateMemberPw(Map<String, Object> map);
<!-- 회원암호변경하기 -->
<update id="updateMemberPw" parameterType="map">
UPDATE MEMBERTBL SET USERPW=#{userpw} WHERE USERID=#{userid}
</update>