게시판 - 3 (수정,삭제) 최종

2021. 8. 14. 15:14(구)공부/SpringBoot

728x90

controller

package com.bit.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
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.servlet.mvc.support.RedirectAttributes;

import com.bit.domain.WebBoard;
import com.bit.persistence.WebBoardRepository;
import com.bit.vo.PageMaker;
import com.bit.vo.PageVO;

import lombok.extern.java.Log;

@Controller
@RequestMapping("/boards")
@Log
public class WebBoardController {

	@Autowired
	private WebBoardRepository repo;

	@GetMapping("/list")
	public void list(@ModelAttribute("pageVO") PageVO vo, Model model) { // boards/list.html 호출
		Pageable page = vo.makePageable(0, "bno");
		Page<WebBoard> result = repo.findAll(repo.makePredicate(vo.getType(), vo.getKeyword()), page);
		log.info("list() called  " + page);
		log.info(result + "");
//		model.addAttribute("result",result);
		model.addAttribute("result", new PageMaker(result));
	}

	@GetMapping("/register")
	public void registerGET(@ModelAttribute("vo") WebBoard vo) {
		log.info("registerGET...");
		vo.setTitle("샘플제목");
		vo.setContent("샘플 내용");
		vo.setWriter("user00");
	}

	@PostMapping("/register")
	public String registerPOST(@ModelAttribute("vo") WebBoard vo, RedirectAttributes rttr) {
		log.info("registerPOST...");
		log.info(vo + "");
		repo.save(vo);
		// addFlashAttribute : url에 붙지 않고, 한번 사용후 데이터 소멸
		rttr.addFlashAttribute("msg", "success");

		vo.setTitle("샘플제목");
		vo.setContent("샘플 내용");
		vo.setWriter("user00");

		return "redirect:/boards/list";
	}

	@GetMapping("/view")
	public void view(Long bno, @ModelAttribute("pageVO") PageVO vo, Model model) {

		log.info("bno : " + bno);
		repo.findById(bno).ifPresent(board -> model.addAttribute("vo", board));

	}

	@GetMapping("/modify")
	public void modifyGET(Long bno, @ModelAttribute("pageVO") PageVO vo, Model model) {
		log.info("modifyGET...");
		repo.findById(bno).ifPresent(board -> model.addAttribute("vo", board));
	}

	@PostMapping("/modify")
	public String modifyPOST(WebBoard board,PageVO vo, RedirectAttributes rttr) {
		log.info("modify WebBoard : " + board);
		repo.findById(board.getBno()).ifPresent(origin->{
			origin.setTitle(board.getTitle());
			origin.setContent(board.getContent());
			repo.save(origin);
			rttr.addFlashAttribute("msg","success");
			rttr.addAttribute("bno",origin.getBno());
		});
		
		//페이징과 검색을 위한 값도 같이 넘어감.
		rttr.addAttribute("page",vo.getPage());
		rttr.addAttribute("size",vo.getSize());
		rttr.addAttribute("type",vo.getType());
		rttr.addAttribute("keyword",vo.getKeyword());
		
		
		return "redirect:/boards/view";
	}
	
	@PostMapping("/delete")
	public String deletePOST(Long bno, PageVO vo, RedirectAttributes rttr) {
		log.info("DELETE Bno : "+bno);
		repo.deleteById(bno);
		rttr.addFlashAttribute("msg","success");
		rttr.addAttribute("page",vo.getPage());
		rttr.addAttribute("size",vo.getSize());
		rttr.addAttribute("type",vo.getType());
		rttr.addAttribute("keyword",vo.getKeyword());
		
		return "redirect:/boards/list";
	}
}

domain/WebBoard

package com.bit.domain;

import java.sql.Timestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;


@Getter @Setter @ToString
@Entity @Table(name = "tbl_webboards")
@EqualsAndHashCode(of="bno")
public class WebBoard {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long bno;
	private String title;
	private String writer;
	private String content;
	
	@CreationTimestamp
	private Timestamp regdate;
	@UpdateTimestamp
	private Timestamp updatedate;
	
}

persistence/WebBoardRepository

package com.bit.persistence;

import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;

import com.bit.domain.QWebBoard;
import com.bit.domain.WebBoard;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.Predicate;

public interface WebBoardRepository extends CrudRepository<WebBoard, Long>, QuerydslPredicateExecutor<WebBoard>{
	
	//java 8.0 : 디폴트 메소드 -> 오버라이딩하지 않으면 이 메소드 그대로 사용
	public default Predicate makePredicate(String type, String keyword) {
		BooleanBuilder builder = new BooleanBuilder();
		QWebBoard board = QWebBoard.webBoard;
		//bno>0
		builder.and(board.bno.gt(0));
		if(type==null) {
			return builder;
		}
		//검색 로직
		switch(type) {
		case "t":
			builder.and(board.title.like("%"+keyword+"%"));
			break;
		case "c":
			builder.and(board.content.like("%"+keyword+"%"));
			break;
		case "w":
			builder.and(board.writer.like("%"+keyword+"%"));
			break;
		}
		
		
		return builder;		
	}
	

}

vo/PageMaker

package com.bit.vo;

import java.util.ArrayList;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import lombok.Getter;
import lombok.ToString;
import lombok.extern.java.Log;

@Getter
@ToString(exclude="pageList")
@Log
public class PageMaker<T> {

	private Page<T> result;
	
	private Pageable prevPage;
	private Pageable nextPage;
	
	private int currentPageNum;
	private int totalPageNum; 
	
	private Pageable currentPage;
	
	private List<Pageable> pageList;
	
	public PageMaker(Page<T> result){
		
		this.result = result;
		
		this.currentPage = result.getPageable();
		
		this.currentPageNum = currentPage.getPageNumber() + 1; 
		
		this.totalPageNum = result.getTotalPages();
		
		this.pageList = new ArrayList<>();
		
		calcPages();
		
	}
	private void calcPages(){
		
		int tempEndNum = (int)(Math.ceil(this.currentPageNum/10.0)* 10);
		
		int startNum = tempEndNum -9; 
		
		Pageable startPage = this.currentPage;
		
		//move to start Pageble 
		for(int i = startNum; i < this.currentPageNum; i++){
			startPage = startPage.previousOrFirst();
		}
		this.prevPage = startPage.getPageNumber() <= 0? null :startPage.previousOrFirst();
		
//		log.info("tempEndNum: " + tempEndNum);
//		log.info("total: "+ totalPageNum);
		
		if(this.totalPageNum < tempEndNum){
			tempEndNum = this.totalPageNum;
			this.nextPage = null;
		}
		
		for(int i = startNum ; i <= tempEndNum; i++){
			pageList.add(startPage);
			startPage = startPage.next();
		}
		this.nextPage = startPage.getPageNumber() +1 <= totalPageNum ? startPage: null;
	}
}

vo/PageVO

package com.bit.vo;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

//브라우저에서 전달되는 페이지 번호와 게시물 수
public class PageVO {
	private static final int DEFAULT_SIZE = 10;
	private static final int DEFAULT_MAX_SIZE=50;
	
	private int page;
	private int size;
	
	private String type;
	private String keyword;
	
	PageVO(){
		this.page = 1;
		size = DEFAULT_SIZE;
	}
	
	public int getPage() {
		return page;
	}

	public int getSize() {
		return size;
	}

	public String getType() {
		return type;
	}
	public void setType(String type) {
		this.type = type;
	}
	public String getKeyword() {
		return keyword;
	}
	
	public void setKeyword(String keyword) {
		this.keyword = keyword;
	}

	


	public void setPage(int page) {
		this.page = page<=0?1:page;
	}
	
	public void setSize(int size) {
		this.size = size < DEFAULT_SIZE || 
				size > DEFAULT_MAX_SIZE ? DEFAULT_SIZE : size;
	}
	
	public Pageable makePageable(int direction, String...props) {
		Sort.Direction dir = direction == 0 ? Sort.Direction.DESC : 
			Sort.Direction.ASC;
		return PageRequest.of(this.page-1, this.size, dir, props);
	}
	
	
}

resources/boards/list.html

<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{/layout/layout1}">

<div layout:fragment="content">

	<div class="panel-heading">List Page</div>

	<div class="panel-body pull-right">
		<h3><a class="label label-default" th:href="@{register}">Register</a></h3>
	</div>

	<div class="panel-body">
		<p>[[${result}]]</p>
		<div th:with="result=${result.result}">
			<table class="table table-striped table-bordered table-hover" id="dataTables-example">
				<thead>
					<tr>
						<th>BNO</th>
						<th>TITLE</th>
						<th>WRITER</th>
						<th>REGDATE</th>
					</tr>
				</thead>

				<tbody>
					<tr class="odd gradeX" th:each="board:${result.content}">
						<td>[[${board.bno}]]</td>
						<td><a th:href="${board.bno}" class="boardLink">
								[[${board.title}]]</a> </td>
						<td>[[${board.writer}]]</td>
						<td class="center">
							[[${#dates.format(board.regdate,'yyyy-MM-dd')}]]</td>
					</tr> 
				</tbody>
				
			</table>
			
			<div>
				<select id='searchType'>
					<option>--</option>
					<option value='t' th:selected="${pageVO.type} =='t'" >Title</option>
					<option value='c' th:selected="${pageVO.type} =='c'">Content</option>
					<option value='w' th:selected="${pageVO.type} =='w'">Writer</option>
				</select>
			  <input type='text' id='searchKeyword' th:value="${pageVO.keyword}">
			  <button id='searchBtn'>Search</button> 
			</div>
			
		</div>
		
		


		<!--paging-->
		<nav>
			<div>
				<ul class="pagination">
					<li class="page-item" th:if="${result.prevPage}">
						<a th:href="${result.prevPage.pageNumber}+1">PREV [[${result.prevPage.pageNumber}+1]]</a>
					</li>

					<li th:classappend="${p.pageNumber==result.currentPageNum-1}?active:' '"
						th:each="p:${result.pageList}">
						<a th:href="${p.pageNumber}+1">[[${p.pageNumber}+1]]</a>
					</li>

					<li class="page-item" th:if="${result.nextPage}">
						<a th:href="${result.nextPage.pageNumber}+1">NEXT [[${result.nextPage.pageNumber}+1]]</a>
					</li>
				</ul>
			</div>
		</nav>

	</div>

	<form id='f1' th:action="@{list}" method="get">
		<input type='hidden' name='page' th:value=${result.currentPageNum}>
		<input type='hidden' name='size' th:value=${result.currentPage.pageSize}>
		<input type='hidden' name='type' th:value=${pageVO.type}>
		<input type='hidden' name='keyword' th:value=${pageVO.keyword}>
	</form>

</div>
<!--  end fragment -->

<th:block layout:fragment="script">

	<script th:inline="javascript">
		var formObj = $("#f1");

		$(window).load(function () {
			var msg = [[${msg}]];
			if (msg == 'success') {
				alert("정상적으로 처리되었습니다.");
				var stateObj = {msg: ""};
			}
		});

		$(document).ready(function () {

			$(".pagination a").click(function (e) {
				e.preventDefault();
				formObj.find('[name="page"]').val($(this).attr('href'));
				formObj.submit();
			});
		});

		$("#searchBtn").click(function (e) {
			var typeStr = $("#searchType").find(":selected").val();
			var keywordStr = $("#searchKeyword").val();
			console.log(typeStr, "", keywordStr);
			formObj.find("[name='type']").val(typeStr);
			formObj.find("[name='keyword']").val(keywordStr);
			formObj.find("[name='page']").val("1");
			formObj.submit();
		});

		$(".boardLink").click(function (e) {
			e.preventDefault();
			var boardNo = $(this).attr("href");
			formObj.attr("action", [[@{'/boards/view'}]]);
			formObj.append("<input type='hidden' name='bno' value='" + boardNo + "'>");
			formObj.submit();
			});


	</script>

</th:block>

./modify.html

<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{/layout/layout1}">

<div layout:fragment="content">

	<div class="panel-heading">Modify Page</div>
	<div class="panel-body">

    <form id='f1'>

		<div class="form-group">
			<label>BNO</label> <input class="form-control" name="bno"
				th:value="${vo.bno}" readonly="readonly" />
		</div>

		<div class="form-group">
			<label>Title</label> <input class="form-control" name="title"
				th:value="${vo.title}" />
			<p class="help-block">Title text here.</p>
		</div>

		<div class="form-group">
			<label>Content</label>
			<textarea class="form-control" rows="3" name='content'
				th:text="${vo.content}"></textarea>
		</div>

		<div class="form-group">
			<label>Writer</label> <input class="form-control" name="writer"
				th:value="${vo.writer}" readonly="readonly" />
		</div>
		
		<input type='hidden' name="page" th:value="${pageVO.page}">
		<input type='hidden' name="size" th:value="${pageVO.size}">
		<input type='hidden' name="type" th:value="${pageVO.type}">
		<input type='hidden' name="keyword" th:value="${pageVO.keyword}">
		
		</form>

		<div class="form-group">
			<label>RegDate</label> <input class="form-control" name="regDate"
				th:value="${#dates.format(vo.regdate,'yyyy-MM-dd')}"
				readonly="readonly" />
		</div>

		<div class="pull-right">
		
		  <a href="#" class="btn btn-warning modbtn">Modify</a>
		
		  <a href="#" class="btn btn-danger delbtn">Delete</a>

			<a th:href="@{ list(page=${pageVO.page}, 
			                size=${pageVO.size}, 
			                type=${pageVO.type}, 
			                keyword=${pageVO.keyword},
			                bno = ${vo.bno}
			             )}" class="btn btn-primary">Cancel & Go List</a> 
		</div>

	</div>

</div>
<!--  end fragment -->

<th:block layout:fragment="script">

	<script th:inline="javascript">
	$(document).ready(function(){
	
		var formObj = $("#f1");
		
		$(".delbtn").click(function(){
			
			formObj.attr("action","delete");
			formObj.attr("method", "post");
			
			formObj.submit();
			
		});
		
		$(".modbtn").click(function(){
			
			formObj.attr("action","modify");
			formObj.attr("method", "post");
			
			formObj.submit();
			
		});
		
		
	});	
	</script>

</th:block>

./register.html

<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{/layout/layout1}">

<div layout:fragment="content">

	<div class="panel-heading">Register Page</div>
	<div class="panel-body">

		<form th:action="@{register}" method="post">

			<div class="form-group">
				<label>Title</label> <input class="form-control" name="title"
					th:value="${vo.title}" />
				<p class="help-block">Title text here.</p>
			</div>

			<div class="form-group">
				<label>Content</label>
				<textarea class="form-control" rows="3" name='content'
					th:text="${vo.content}"></textarea>
			</div>

			<div class="form-group">
				<label>Writer</label> <input class="form-control" name="writer"
					th:value="${vo.writer}" />
			</div>
			<button type="submit" class="btn btn-default">Submit Button</button>
			<button type="reset" class="btn btn-primary">Reset Button</button>
		</form>

	</div>

</div>
<!--  end fragment -->

<th:block layout:fragment="script">

	<script th:inline="javascript">

	</script>

</th:block>

./view.html

<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	layout:decorate="~{/layout/layout1}">

<div layout:fragment="content">

	<div class="panel-heading">View Page</div>
	<div class="panel-body">


		<div class="form-group">
			<label>BNO</label> <input class="form-control" name="bno"
				th:value="${vo.bno}" readonly="readonly" />
		</div>

		<div class="form-group">
			<label>Title</label> <input class="form-control" name="title"
				th:value="${vo.title}" readonly="readonly" />
			<p class="help-block">Title text here.</p>
		</div>

		<div class="form-group">
			<label>Content</label>
			<textarea class="form-control" rows="3" name='content'
				th:text="${vo.content}" readonly="readonly"></textarea>
		</div>

		<div class="form-group">
			<label>Writer</label> <input class="form-control" name="writer"
				th:value="${vo.writer}" readonly="readonly" />
		</div>

		<div class="form-group">
			<label>RegDate</label><input class="form-control" name="regDate"
				th:value="${#dates.format(vo.regdate,'yyyy-MM-dd')}"
				readonly="readonly" />
		</div>

		<div class="pull-right">
			<a th:href="@{modify(page=${pageVO.page}, 
			                size=${pageVO.size}, 
			                type=${pageVO.type}, 
			                keyword=${pageVO.keyword},
			                bno =${vo.bno}
			             )}" class="btn btn-default">Modify/Delete</a> 
			             
			<a th:href="@{list(page=${pageVO.page}, 
			                size=${pageVO.size}, 
			                type=${pageVO.type}, 
			                keyword=${pageVO.keyword},
			                bno = ${vo.bno}
			             )}" class="btn btn-primary">Go List</a> 
		</div>

	</div>

</div>
<!--  end fragment -->

<th:block layout:fragment="script">

	<script th:inline="javascript">
	
	$(window).load(function(){
			var msg = [[${msg}]];
			if(msg =='success') {
				alert("정상적으로 처리되었습니다.");
				var stateObj = { msg: "" };
			}
		});
		
	</script>

</th:block>

resources/templates/layout.html

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">

<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="apple-touch-icon" href="apple-touch-icon.png">
<!-- Place favicon.ico in the root directory -->

<link rel="stylesheet" th:href="@{/css/normalize.css}">
<link rel="stylesheet" th:href="@{/css/main.css}">
<script th:src="@{/js/vendor/modernizr-3.11.2.min.js}"></script>
</head>
<body>

	<div class="page-header">
		<h1>
			Boot06 Project <small>for Spring MVC + JPA</small>
		</h1>
	</div>

	<div class="panel panel-default" layout:fragment="content">
		<div class="panel-body">Web Board List Page</div>
	</div>
	<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>

	<script>
		window.jQuery
				|| document
						.write(
								'<script th:src="@{/js/vendor/jquery-1.12.0.min.js}"><\/script>')
	</script>
	<script th:src="@{/js/plugins.js}"></script>
	<script th:src="@{/js/main.js}"></script>

	<!-- Latest compiled and minified CSS -->
	<link rel="stylesheet"
		href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
		integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
		crossorigin="anonymous">

	<!-- Optional theme -->
	<link rel="stylesheet"
		href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
		integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp"
		crossorigin="anonymous">

	<!-- Latest compiled and minified JavaScript -->
	<script
		src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
		integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
		crossorigin="anonymous"></script>

	<!-- custom javascript  -->
	<th:block layout:fragment="script"></th:block>


	<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
	<script>
		(
								function(b, o, i, l, e, r) {
									b.GoogleAnalyticsObject = l;
									b[l] || (b[l] = function() {
										(b[l].q = b[l].q || []).push(arguments)
									});
									b[l].l = +new Date;
									e = o.createElement(i);
									r = o.getElementsByTagName(i)[0];
									e.src = 'https://www.google-analytics.com/analytics.js';
									r.parentNode.insertBefore(e, r)
								}(window, document, 'script', 'ga'));
		ga('create', 'UA-XXXXX-X', 'auto');
		ga('send', 'pageview');
	</script>
</body>
</html>

properties



spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/jpadb?useUnicode=yes&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=1234

spring.jpa.hibernate.ddl-auto=update
spring.jpa.generate-ddl=false
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
logging.level.org.hibernate=info
server.port=8888
spring.thymeleaf.cache=false
logging.level.org.springframework.web=info
logging.level.com.bit=info

pom.xml

		<!-- https://mvnrepository.com/artifact/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect -->
		<dependency>
		    <groupId>nz.net.ultraq.thymeleaf</groupId>
		    <artifactId>thymeleaf-layout-dialect</artifactId>
		    <version>2.5.3</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/com.querydsl/querydsl-jpa -->
		<dependency>
		    <groupId>com.querydsl</groupId>
		    <artifactId>querydsl-jpa</artifactId>
		    <version>4.4.0</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/com.querydsl/querydsl-apt -->
		<dependency>
		    <groupId>com.querydsl</groupId>
		    <artifactId>querydsl-apt</artifactId>
		    <version>4.4.0</version>
		</dependency>
        
        
        
        
        
        
        
        	<plugin>
				<groupId>com.mysema.maven</groupId>
				<artifactId>apt-maven-plugin</artifactId>
				<version>1.1.3</version>
				<executions>
					<execution>
						<goals>
							<goal>process</goal>
						</goals>
						<configuration>
							<outputDirectory>target/generated-sources/java</outputDirectory>
							<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
						</configuration>
					</execution>
				</executions>
			</plugin>

 

728x90

'(구)공부 > SpringBoot' 카테고리의 다른 글

오류  (0) 2021.11.14
entity 설정  (0) 2021.11.10
게시판 만들기 - 2(페이징)  (2) 2021.08.12
게시판 만들기 - 1  (0) 2021.08.11
Thymeleaf 입문  (0) 2021.08.10