20220926 [Spring Boot, H2DB, MyBatis]

Yeoonnii·2022년 9월 26일
0

TIL

목록 보기
34/52
post-thumbnail

관리자 게시물 일괄삭제

admin/home.html

스크립트를 통해 action이 바뀌며 주어진 type값에 따라 주소가 다르게 이동한다

  • 스크립트 변수명을 따로주어 주소를 따로 이동할 수도 있다 이런경우 type값도 필요없다!
  • 스크립트 변수명을 동일하게 주면 주소를 동일하게 사용가능하고,
    스크립트 변수명을 따로 주는 경우 원하는 주소를 각각 지정하여 사용가능하다
    💡 사용자 스타일, 상황, 필요에 따라 다르게 사용하면 된다!
<div th:if="${menu ==2}">
        게시물관리
        <form th:action="@{/admin/스크립트에서변경됨.do}" method="post" id="form">
            <table border="1">
                <tr>
                    <th> </th>
                    <th>글번호</th>
                    <th>제목</th>
                    <th>작성자</th>
                    <th>내용</th>
                    <th>조회수</th>
                    <th>등록일</th>
                </tr>

                <tr th:each="obj, idx : ${list}">
                    <td><input style="width: 20px;" type="checkbox" name="chk" th:value="${obj.no}" /></td>
                    <td><input style="width: 20px;" type="text" name="no" th:value="${obj.no}" readonly /></td>
                    <td><input style="width: 100px;" type="text" name="title" th:value="${obj.title}"  /></td>
                    <td><input style="width: 100px;" type="text" name="writer" th:value="${obj.writer}" /></td>
                    <td><input style="width: 150px;" type="text" name="content" th:value="${obj.content}" /></td>
                    <td><input style="width: 20px;" type="text" name="hit" th:value="${obj.hit}" readonly /></td>
                    <td><input style="width: 100px;" type="text" name="regdate" th:value="${obj.regdate}" readonly/></td>
                </tr>
            </table>
            <input type="button" value="일괄삭제" th:onclick="|javascript:handleContent(1)|" />
            <input type="button" value="일괄수정" th:onclick="|javascript:handleContent(2)|" />
        </form>
    </div>
...
<script>
...
// 게시물 일괄삭제/일괄수정
        const handleContent = (type) => {
            // form 찾기
            const form = document.getElementById('form');

            if (type == 1) { //type 1인경우
                form.action = "[[@{/admin/membercontent.do(type=1)}]]";
            }
            else if (type == 2) {
                form.action = "[[@{/admin/membercontent.do(type=2)}]]";
            }
            form.submit();
        }
    </script>
...

AdminController.java

  • 체크박스로 체크된 항목만 가져오면 된다
    ➡️ @RequestParam(name = "chk") Long[] chk로 번호값들을 받아온다
    @RequestParam(name = "chk") List<Long> chk List형태로도 전송 가능!
  • 체크된 항목만 찾아서 List<BoardDTO> 로 변환하여 mapper로 넘겨준다
// 게시물 관리
        // 일괄삭제 /membercontent.do + type=1
        @PostMapping(value = "/membercontent.do")
        public String memberContentDelete(
            @RequestParam(name = "chk") Long[] chk,
            @RequestParam(name = "type") int type
            ){
            // 번호 잘 오는지 출력하여 확인!
            // System.out.println(chk.toString());

            if(type == 1){ // 일괄삭제
                // xml에서 필요한 chk보내줌
                int result = bmapper.contentDelete(chk);
            }
...
		return "redirect:/admin/home.do?menu=2";

BoardMapper.java

일괄삭제 ➡️ 배열형태로 보냄
list형태로 보내는 경우 = public int contentDelete( List<Long> chk);

public int contentDelete(Long[] chk);

boardMapper.xml

WHERE NO IN (1,2,..)의 형태이니 구분자는 , = separator=","

<!-- 선택된 게시글 삭제하기 -->
    <delete id="contentDelete" parameterType="list">
        DELETE BOARDTBL WHERE NO IN (
        <foreach collection="list" item="tmp" separator=",">
        #{tmp}
        </foreach>
        )
    </delete>

관리자 게시물 일괄수정

체크박스에 관계없이 전부 수정할것인지, 체크박스 선택된 게시물만 변경할것인지에 따라 넘겨줄 정보가 달라진다

체크박스 선택된것만 변경해보기 ➡️ 체크박스의 선택된 번호를 넘겨줘야 한다

admin/home.html

수정될 항목에 name값 지정하여 값을 넘겨준다

	<td><input style="width: 20px;" type="checkbox" name="chk" th:value="${obj.no}" /></td>
	<td><input style="width: 20px;" type="text" name="no" th:value="${obj.no}" readonly /></td>
	<td><input style="width: 100px;" type="text" name="title" th:value="${obj.title}"  /></td>
	<td><input style="width: 100px;" type="text" name="writer" th:value="${obj.writer}" /></td>
	<td><input style="width: 150px;" type="text" name="content" th:value="${obj.content}" /></td>
	<td><input style="width: 20px;" type="text" name="hit" th:value="${obj.hit}" readonly /></td>
	<td><input style="width: 100px;" type="text" name="regdate" th:value="${obj.regdate}" readonly/></td>

AdminController.java

name으로 지정된 수정할 값을 받아온다
체크박스가 체크된 항목만 넘어옴
➡️ 반복문으로 chk[i] 와 no[j]가 일치하는 경우 찾기
➡️ 일치하는 경우 해당 번호의 데이터만 수정하여 List<BoardDTO> 에 추가

@PostMapping(value = "/membercontent.do")
    public String memberContentDelete(
            @RequestParam(name = "chk") Long[] chk,
            @RequestParam(name = "no") Long[] no,
            @RequestParam(name = "title") String[] title,
            @RequestParam(name = "writer") String[] writer,
            @RequestParam(name = "content") String[] content,
            @RequestParam(name = "hit") Long[] hit,
            @RequestParam(name = "type") int type
        ){
...
	else if(type ==2){ //일괄수정
		List<BoardDTO> list = new ArrayList<>();
			for(int i=0; i<chk.length; i++){ //비교 대상
				for(int j=0; j<no.length; j++){ //찾은 대상
					if(chk[i] == no[j]) {
						BoardDTO board = new BoardDTO();
						board.setNo(no[j]);
						board.setTitle(title[j]);
						board.setWriter(writer[j]);
						board.setHit(hit[j]);
						list.add(board);
                        }
                	}
            	}
			// 체크된 항목만 리스트 객체로 생성되었는지 출력하여 확인
			System.out.println(list.toString());
			System.out.println("chk =>" + chk.toString());
			bmapper.updateBoard(list);
		}
        return "redirect:/admin/home.do?menu=2";

BoardMapper.java

일괄수정하기

public int updateBoard(List<BoardDTO> list);

boardMapper.xml

💡 separator(구분자) 사용기준
Mybatis에서 여러줄의 작업을 반복문<foreach>을 사용하여 축약하는 경우 구분자를 따로 명시해주고 반복문을 실행하게 된다
➡️ 결과적으로 반복문 1회 실행 + 구분자 형태가 생성이 된다
ex) 여러개의 데이터를 수정하는 경우 아래 SQL문에서 WHEN은 줄바꿈으로 수정정보가 구분되고 있다! 그렇기 때문에 MyBatis에서는 separator=" " 구분자를 공백으로 사용해준다

UPDATE MEMBERTBL SET
BLOCK = (CASE
WHEN USERID='변경아이디' THEN 0
WHEN USERID='변경아이디' THEN 0
END)
WHERE USERID IN('변경아이디','변경아이디');

⇒ SQL문을 풀어서 써보면 띄어쓰기로 구분되는것을 확인할 수 있다
= 구분자기준은 SQL문

<!-- 선택된 게시글 수정하기 -->
    <update id="updateBoard" parameterType="list">
        UPDATE BOARDTBL SET
            TITLE = (CASE
                <foreach collection="list" item="obj" separator="">
                    WHEN NO=#{obj.no} THEN #{obj.title}
                </foreach>
            END),
            WRITER = (CASE
                <foreach collection="list" item="obj" separator="">
                    WHEN NO=#{obj.no} THEN #{obj.writer}
                </foreach>
            END),
            CONTENT = (CASE 
                <foreach collection="list" item="obj" separator="">
                    WHEN NO=#{obj.no} THEN #{obj.content}
                </foreach>
            END),
            HIT = (CASE 
                <foreach collection="list" item="obj" separator="">
                    WHEN NO=#{obj.no} THEN #{obj.hit}
                </foreach>
            END)
        WHERE NO IN (
            <foreach collection="list" item="obj" separator=",">
                    #{obj.no}
                </foreach>
        )
    </update>

관리자 게시물 일괄추가

admin/home.html

일괄추가 버튼 생성 ➡️ 클릭시 게시물 일괄추가 페이지로 이동

<a th:href="@{/admin/boardInsert}"><input type="button" value="일괄추가" /></a>

AdminController.java

// 게시글 일괄등록페이지로 이동
    @GetMapping(value = "/boardInsert.do")
    public String boardInsertGET(){
        return "admin/boardinsert";
    }

    // 게시글 일괄등록 수행하기
    @PostMapping(value = "/boardInsert.do")
    public String boardInsertPOST(){
        return "redirect:/admin/home.do?menu=2";
    }

admin/boardinsert.html

게시물 일괄추가 페이지 생성

Thymeleaf로 테이블 형태 출력시
<tr:block> 사용하면 테이블 형태 출력이 안됨
➡️ <tr> 사용해야 반복문에서 테이블 형태 출력가능

<!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(menu=2)}"><button>게시물 관리 페이지로</button></a>
    <hr />
    <h3>게시물 일괄추가</h3>
        <form th:action="@{/admin/boardInsert.do}" method="post" id="form">
            <table border="1">
                <tr>
                    <th>제목</th>
                    <th>작성자</th>
                    <th>내용</th>
                </tr>

                <tr th:each="i : ${#numbers.sequence(1,5)}">
                    <td><input style="width: 100px;" type="text" name="title" th:value="|제목 ${i}|"  /></td>
                    <td><input style="width: 100px;" type="text" name="writer" th:value="|내용 ${i}|" /></td>
                    <td><input style="width: 150px;" type="text" name="content" th:value="|작성자 ${i}|" /></td>
                </tr>
            </table>
            <input type="submit" value="일괄추가" />
        </form>
    </div>
</body>
</html>

AdminController.java

@RequestParam으로 등록할 값을 받아온다
List를 생성하여 boardDTO타입으로 받아온 데이터 담고 넘겨준다!

// 게시글 일괄등록 수행하기
    @PostMapping(value = "/boardInsert.do")
    public String boardInsertPOST(
        @RequestParam(name = "title") String[] title,
        @RequestParam(name = "content") String[] content,
        @RequestParam(name = "writer") String[] writer){
            List<BoardDTO> list = new ArrayList<>();
            for( int i=0; i<title.length; i++){
                BoardDTO board = new BoardDTO();
                board.setTitle(title[i]);
                board.setContent(content[i]);
                board.setWriter(writer[i]);

                list.add(board);
            }
            int ret = bmapper.insertBoardAll(list);
            System.out.println(ret);
        return "redirect:/admin/home.do?menu=2";
    }

BoardMapper.java

public int insertBoardAll(List<BoardDTO> list);

boardMapper.xml

시퀀스 이용하여 게시물 등록하는 경우

  • 현재 H2DB 사용하기 때문에 함수 생성 불가
  • H2DB 오라클 문법 사용불가! 자바문법 사용하기 때문에
    H2DB에서는 UNION ALL도 그냥은 사용불가 ➡️ 변형하여 사용은 가능

데이터 일괄 등록시
INSERT ALL (시퀀스 바로 사용불가, 시퀀스 함수 생성 후 사용)
UNOIN ALL (시퀀스가 있는 경우) UNION ALL ${ }로 변경하여 넣을 수 있다!
ITEMMAPPER.XML에서 UNION ALL 사용하여 데이터 일괄등록 했었음

지금 사용한 방법은 중간에 일부는 입력되고 나머지 일부는 입력 안될수도 있어서 사용하지 않는 것이 좋다

separator=";" ⇒ 한줄씩 등록이니까 구분자에 세미콜론이 들어가야 한다

<!-- 게시글 일괄등록하기 -->
    <insert id="insertBoardAll" parameterType="list">
        <foreach collection="list" item="obj" separator=";">
        INSERT INTO BOARDTBL(NO, TITLE, WRITER, HIT, REGDATE, CONTENT)
        VALUES( SEQ_BOARDTBL_NO.NEXTVAL , #{obj.title}, #{obj.writer}, 1, CURRENT_DATE, #{obj.content})
        </foreach>
    </insert>

MyBatis에서 # , $ 사용

#{ } 사용하는 경우 = (value)값 넣을 때 = { }안의 값이 데이터 값이 되는 경우

INSERT INTO 테이블 VALUES ( #{  }, ) ;

${ } 사용하는 경우 = { }안의 값이 테이블명 또는 컬럼명이 되는 경우
예를 들어 조회시 테이블의 컬럼명만 바꿔가며 조회하고 싶은 경우 사용할 수 있다
➡️ 테이블 명이나 컬럼명을 고정해서 쿼리를 작성했다면 쿼리문이 길어질 수 있는 단점을 해결할 수 있음
SELECT * FROM 테이블명 WHERE ${ 컬럼명 } LIKE ‘%‘ || ‘A’ || ‘%’

${ } 형태로 사용하는 경우 컬럼명만 그대로 출력된다
따옴표로 감싸 사용해 주어야 한다 ⇒ ‘${ }’
EX ⇒ SELECT '${title}' TITLE, '${writer}' WRITER FROM DUAL …

SELECT * FROM 테이블명 WHERE NO LIKE ‘%‘ || ‘A’ || ‘%’
➡️ 번호 컬럼에서 A를 검색하는 경우
SELECT * FROM 테이블명 WHERE TITLE LIKE ‘%‘ || ‘A’ || ‘%’
➡️ 제목 컬럼에서 A를 검색하는 경우
SELECT * FROM 테이블명 WHERE WRITER LIKE ‘%‘ || ‘A’ || ‘%’
➡️ 작성자 컬럼에서 A를 검색하는 경우


RESTFUL 사용하기

현재 CDN방식 ⇒ BACKEND에서 직접HTML로 전송! View 연동하는방식은 아니다

VIEW에 데이터를 전송해주는 RESTFUL의 개념에 더 집중해야한다

RESTFUL 사용한다 = 지금 TEMPLATE 파일(html)사용 안한다

페이지 내부 이동시
주소 새로고침이 안되면 프론트 사용중 ⇒ 마켓컬리
주소 새로고침되면 cdn방식 사용중 ⇒ 금융권

크로스도메인(Cross Domain) 문제 해결

크로스도메인(Cross Domain) = 서로 다른 도메인간의 호출을 말한다

웹 브라우저 Javascript를 이용하여 다른 도메인의 서버 URL을 호출하여 데이터를 가져오는 경우, 보안 문제가 발생된다

웹은 vue를 빌더 시켜서 templates에 포함을 시키기 때문에 cros문제가 없음
➡️ ios, android등 다른 장치에서 동작되면 접근불가

크로스도메인 문제 해결하려면 RestController 파일에 @CrossOrign(”*”) 을 추가해준다

Boot20220915Application.java

restcontroller 파일 ⇒ 임의로 만들었으니 위치 등록

"com.example.restcontroller",

BoardRestController.java

restcontroller 파일 생성
@CrossOrigin("*") 크로스도메인(Cross Domain) 문제를 해결함, 세밀하게 설정가능

생성된 mapper를 map에 담아서 반환 하면 된다

@Autowired
    BoardMapper bMapper;

데이터 보내기 test

  • consumes = 전송받는 type 정하기
    ➡️ vue에서는 application/json(이미지 없는경우) 또는 form-data(이미지 있는 경우) 타입을 사용한다
  • produces = 전달하는 type, vue에서 받는 타입
    // 127.0.0.1:8080/BOOT1/api/test1.json
    // 데이터 보내기 test
    @GetMapping(value = "/test1.json", consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public Map<String, Object> test1() {
        Map<String, Object> retMap = new HashMap<>();
        retMap.put("key", "test");
        retMap.put("name", "가나다");

        return retMap;
    }

조회수 업데이트 하기

크롬에서는 get만 동작 가능! put은 postman에서 확인하기

    // 127.0.0.1:8080/BOOT1/api/board/updatehit.json?no=2
    @PutMapping(value = "/updatehit.json")
    public Map<String, Object> updateHit(
        @RequestParam(name = "no") Long no
        ){
        Map<String, Object> retMap = new HashMap<>();
        try {
            int ret = bMapper.updateBoardHit(no);
            retMap.put("status", 200);
            retMap.put("result", ret);
            
        } catch (Exception e) {
            e.printStackTrace();
            retMap.put("status", 0);
        }
        return retMap;
    }

💡 security csrf 때문에 확인 불가
보안의 이유로 rest사용시는 csrf 를 security config에서 해제 해줘야 한다

SecurityConfig.java

// api의 주소만 csrf를 해제
            http.csrf().ignoringAntMatchers("/api/**"); ///api/로 시작하는 것만 csrf를 해제한다
            http.headers().frameOptions().sameOrigin();

csrf사용 해제 후 다시 시도해보면 조회수가 증가하는것을 확인할 수 있다

실행시 업데이트가 이뤄지지 않은 경우 result = 0이 출력된다
ex) 존재하지 않는 글번호의 hit 증가시키기

글목록 조회하기(페이지네이션 + 검색기능)

get이니 크롬 주소창에서도 확인가능

    // 127.0.0.1:8080/BOOT1/api/board/selectlist.json?text=&start=1
    @GetMapping(value = "/selectlist.json")
    public Map<String, Object> selectList(
            @RequestParam(name = "text") String text,
            @RequestParam(name = "start") int start) {
        Map<String, Object> retMap = new HashMap<>();
        try {
            // 1페이지일때 (1-1)*10 => 1
            // 2페이지일때 (2-1)*10 => 11
            // 3페이지일때 (3-1)*10 => 21

            List<BoardDTO> list = bMapper.searchBoardPage(text, (start - 1) * 10 + 1);

            retMap.put("status", 200);
            retMap.put("list", list);
        } catch (Exception e) {
            e.printStackTrace();
            retMap.put("status", 0);
        }
        return retMap;
    }


화면 출력용 컨트롤러 이용하기

  1. 원래는 View에서 구현해야 되는 부분
  2. 화면 출력용 컨트롤러에서는 Model객체를 통해서 view로 값 전달하지 않음

게시물 전체조회

ViewController.java

// 게시판 목록 출력
    @GetMapping(value = "/boardlist.do")
    public String boardList(){
        return "vue/boardlist";
    }

cdn에서 axios사용하기

axios cdn https://cdnjs.com/libraries/axios

axios.min.js 복사해서 <script> 태그에 추가

templates/vue/boardlist.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="@{/vue/boardinsert.do}"><button>글쓰기</button></a>
    <hr />

    <table border="1">
        <thead>
            <th>글번호</th>
            <th>글제목</th>
            <th>작성자</th>
            <th>글내용</th>
            <th>조회수</th>
            <th>등록일</th>
        </thead>
        <tbody id="tbody">
            
        </tbody>
    </table>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
    <script>
        const tbody = document.getElementById("tbody");
        const handleData = async() => {
            const text = '';
            const start = 1;
            const url = `/BOOT1/api/board/selectlist.json?text=${text}&start=${start}`;
            const headers = {"Content-Type":"application/json"};
            const { data } = await axios.get(url, {headers});
            console.log(data);
            
            if(data.status === 200){
                for( let i=0; i<data.list.length; i++ ){
                    tbody.innerHTML +=
                    `<tr>` +
                        `<td>${ data.list[i].no }</td>` + 
                        `<td>${ data.list[i].title }</td>` + 
                        `<td>${ data.list[i].writer }</td>` + 
                        `<td>${ data.list[i].content }</td>` + 
                        `<td>${ data.list[i].hit }</td>` + 
                        `<td>${ data.list[i].regdate }</td>` + 
                    `</tr>`;
                }
            }
        }
        handleData(); //함수는 호출되어야 실행됨

        </script>
</body>
</html>


게시글 작성하기

ViewController.java

// 게시판 글쓰기 페이지 이동
    @GetMapping(value = "/boardinsert.do")
    public String boardInsert(){
        return "vue/boardinsert";
    }

// 게시판 글쓰기 DB등록
    @PostMapping(value="/boardinsert.do")
    public String boardInsert(@RequestBody BoardDTO board) {
        return "vue/boardlist";
    }

templates/vue/boardinsert.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>
    <hr />

    제목<input type="text" id="title" /><br />
    내용<input type="text" id="content" /><br />
    작성자<input type="text" id="writer" /><br />
    <button th:onclick="|javascript:handleInsert()|">글쓰기</button>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
    <script>
        const title = document.getElementById("title");
        const content = document.getElementById("content");
        const writer = document.getElementById("writer");

        const handleInsert = async() => {
            const url = `[[@{/api/board/insertBoard.json}]]`;
            const headers = {"Content-Type":"application/json"};
            const body = {
                title : title.value,
                content : content.value,
                writer : writer.value,
            }
            console.log(body);
            const { data } = await axios.post(url, body, {headers});
            console.log(data);

            if(data.status === 200){
                window.location.href='[[@{/vue/boardlist.do}]]';
            }
        }
    </script>
</body>

결과 사진

글쓰기 화면

글쓰기 완료 후 메인으로 이동

0개의 댓글