스크립트를 통해 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>
...
- 체크박스로 체크된 항목만 가져오면 된다
➡️@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";
일괄삭제 ➡️ 배열형태로 보냄
list형태로 보내는 경우 =public int contentDelete( List<Long> chk);
public int contentDelete(Long[] chk);
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>
체크박스에 관계없이 전부 수정할것인지, 체크박스 선택된 게시물만 변경할것인지에 따라 넘겨줄 정보가 달라진다
체크박스 선택된것만 변경해보기 ➡️ 체크박스의 선택된 번호를 넘겨줘야 한다
수정될 항목에 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>
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";
일괄수정하기
public int updateBoard(List<BoardDTO> list);
💡
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>
일괄추가 버튼 생성 ➡️ 클릭시 게시물 일괄추가 페이지로 이동
<a th:href="@{/admin/boardInsert}"><input type="button" value="일괄추가" /></a>
// 게시글 일괄등록페이지로 이동
@GetMapping(value = "/boardInsert.do")
public String boardInsertGET(){
return "admin/boardinsert";
}
// 게시글 일괄등록 수행하기
@PostMapping(value = "/boardInsert.do")
public String boardInsertPOST(){
return "redirect:/admin/home.do?menu=2";
}
게시물 일괄추가 페이지 생성
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>
@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";
}
public int insertBoardAll(List<BoardDTO> list);
시퀀스 이용하여 게시물 등록하는 경우
- 현재 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>
#{ }
사용하는 경우 = (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를 검색하는 경우
현재 CDN방식 ⇒ BACKEND에서 직접HTML로 전송! View 연동하는방식은 아니다
VIEW에 데이터를 전송해주는 RESTFUL의 개념에 더 집중해야한다
RESTFUL 사용한다 = 지금 TEMPLATE 파일(html)사용 안한다
페이지 내부 이동시
주소 새로고침이 안되면 프론트 사용중 ⇒ 마켓컬리
주소 새로고침되면 cdn방식 사용중 ⇒ 금융권
크로스도메인(Cross Domain) = 서로 다른 도메인간의 호출을 말한다
웹 브라우저 Javascript를 이용하여 다른 도메인의 서버 URL을 호출하여 데이터를 가져오는 경우, 보안 문제가 발생된다
웹은 vue를 빌더 시켜서 templates에 포함을 시키기 때문에 cros문제가 없음
➡️ ios, android등 다른 장치에서 동작되면 접근불가
크로스도메인 문제 해결하려면 RestController 파일에 @CrossOrign(”*”)
을 추가해준다
restcontroller 파일 ⇒ 임의로 만들었으니 위치 등록
"com.example.restcontroller",
restcontroller 파일 생성
@CrossOrigin("*")
크로스도메인(Cross Domain) 문제를 해결함, 세밀하게 설정가능
생성된 mapper를 map에 담아서 반환 하면 된다
@Autowired
BoardMapper bMapper;
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에서 해제 해줘야 한다
// 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;
}
- 원래는 View에서 구현해야 되는 부분
- 화면 출력용 컨트롤러에서는 Model객체를 통해서 view로 값 전달하지 않음
// 게시판 목록 출력
@GetMapping(value = "/boardlist.do")
public String boardList(){
return "vue/boardlist";
}
axios cdn https://cdnjs.com/libraries/axios
axios.min.js 복사해서 <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">
<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>
// 게시판 글쓰기 페이지 이동
@GetMapping(value = "/boardinsert.do")
public String boardInsert(){
return "vue/boardinsert";
}
// 게시판 글쓰기 DB등록
@PostMapping(value="/boardinsert.do")
public String boardInsert(@RequestBody BoardDTO board) {
return "vue/boardlist";
}
글쓰기 페이지 생성 + 스크립트를 이용하여 데이터 전송
<!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>
결과 사진
글쓰기 화면
글쓰기 완료 후 메인으로 이동