20220922 [Spring Boot, H2DB, MyBatis]

Yeoonnii·2022년 9월 22일
0

TIL

목록 보기
32/52
post-thumbnail

고객 물품 주문하기

content.html

  • 물품 상세페이지에서 주문하기 버튼 클릭시
    <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>

H2DB

  • 주문번호용 시퀀스 생성
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
);

OredeDTO.java

DTO생성

@Getter
@Setter
@ToString
@NoArgsConstructor
public class OredeDTO {
    long no;
    long cnt;
    long itemno;
    String userid;
    Date regdate;
}

CustomerController.java

  • 컨트롤러 생성시 어떤 데이터를 파라미터로 넘겨 받을지 고민해야한다
  • ORDERTBL에서 사용자 아이디를 확보하기 위해,
    주문전에 사용자가 로그인된 상태에서 주문 가능하게 만들었어야 한다
    ➡️ 그래야 주문시 세션에서 사용자 id확보 가능
  1. form을 통해서 물품번호/수량 = 2개의 전달값 받기
  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";
    }

OrderMapper.java

Mapper 주문등록 생성
➡️ insert니까 int로 반환

@Mapper
public interface OrderMapper {
    // 주문등록
    public int insertOrder (OrderDTO order);
    }

orderMapper.xml

주문 등록하기

    <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 생성

H2DB

  • 주문내역 조회 후 return 타입으로 ORDERDTO를 보내면 고객이 주문내역을 확인하기에는 데이터가 부족하다
    ➡️ 원하는 데이터를 추가로 조건부 조회하여 VIEW 생성
  • VIEW는 조회만 가능한 가상의 테이블이므로 삭제나 추가는 VIEW에서 불가능
    ➡️ 추가, 수정 삭제등의 작업은 원본에 해당하는 데이터에서 수행해야하며, 원본테이블의 데이터 변화가 일어나는 경우 VIEW에서는 달라지는 조회결과만 확인할 수 있다
  1. ITEMTBLORDERTBL JOIN
SELECT
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;
🖨️ 결과

OrderMapper.java

고객용 주문내역 생성

Mapper에서 조회된 데이터를 보낼때 DTO 또는 MAP을 사용한다

  • Map<String, Object> = 변수명(key)과 type 정의 되어있지 않음
  • DTO = 변수명(key)과 type 정의 되어있음
    ➡️ DTO로 데이터 보내고 싶은 경우, 별도의 주문내역 조회용DTO를 따로 생성하여 DTO타입의 데이터를 전송한다

orderMapper.xml

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>

CustomerController.java

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";
    }

customer/orderlist.html

...
    <!-- 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>
...

고객 주문내역 조회시 이미지 출력

CustomerController.java

기존의 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());
        }

customer/orderlist.html

<th>이미지</th>
	<td>
		<img th:src="@{ ${obj.IMAGE} }" style="width: 50px; height:50px" />
	</td>

고객 주문내역 취소

체크박스로 주문취소하기

orderlist.html

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>

CustomerController.java

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";
    }

OrderMapper.java

배열로 받아온 주문번호를 넘겨주면 삭제결과를 int로 반환

    // 고객주문 주문취소
    public int deleteOrder ( Long[] no );

orderMapper.xml

여러개의 주문번호를 배열로 받아와 삭제시 두가지 방법을 사용할 수 있다
➡️ 좀더 간결한 방법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>

고객 마이페이지

customer/home.html

<!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>

고객 회원정보 수정

update.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>

CustomerController.java

회원정보 수정페이지 이동시 기존 회원정보 데이터 불러오기

// 회원정보수정을 위한 회원정보 조회
    @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";	
    }

MemberMapper.java

Controller에서 반환값 int로 지정
➡️ 조회 제외한 나머지 수정, 삭제 등록은 자동 int반환이라 생각하면 됨

// 회원정보 수정하기
    public int updateinfoMember( MemberDTO member );

memberMapper.xml

 <!-- 회원정보 수정하기 -->
    <update id="updateinfoMember" parameterType="com.example.dto.MemberDTO">
        UPDATE MEMBERTBL SET
            AGE=#{age}, PHONE=#{phone}, GENDER=#{gender}
        WHERE
            USERID=#{userid}
    </update>

CustomerController.java

회원정보 수정 완료시 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";
    }

alert.html

CustomerController에서 model에 담아 보내준 msgurlscript로 출력

<!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>

고객 암호변경

CustomerController.java

암호변경페이지로 이동

    @GetMapping(value="/updatepw.do")
    public String updatepwGET() {
        return "customer/updatepw";
    }

customer/updatepw.html

암호변경시 입력값 전달을 위해 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>

CustomerController.java

입력된 기존암호와 변경할 새 암호 받아와서 DB의 사용자 암호 변경하기
➡️ 기존 암호를 가져와서 변경하는 경우 hash값이 계속 변경되기 때문에 암호 비교 불가능

로직

  1. 로그인 된 아이디를 이용해서 기존 아이디, 암호를 가져옴
  2. 기존 DB의 암호와 현재 암호를 비교하여 일치 여부 확인
  3. 암호일치여부 통과시 암호변경 수행
    3-1. 암호변경시 Map을 새로 생성하여 기존 아이디와 바꿀 암호값을 넣어준다
    3-2. 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";
    }

MemberMapper.java

    // 암호변경
    public int updateMemberPw(Map<String, Object> map);

memberMapper.xml

    <!-- 회원암호변경하기 -->
    <update id="updateMemberPw" parameterType="map">
        UPDATE MEMBERTBL SET USERPW=#{userpw} WHERE USERID=#{userid}
    </update>

0개의 댓글