Spring/Spring framework

boot06 부트스트랩 이용한 게시판 만들기

원2 2021. 8. 11. 17:20
728x90
반응형

기본 세팅 pm.xml

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>

pom.xml 의 plugin

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

플러그인을 추가하면 자동으로 생성된다.

안생긴다면 프로젝트의 메이븐을 업데이트!


프로젝트 구성


WebBoardController.java

 

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;
	
//	boards/list.html호출
	@GetMapping("/list")
		public void list(@ModelAttribute("pageVO") PageVO vo, Model model) {
			Pageable page = vo.makePageable(0, "bno");
			log.info("*****************" + vo.getKeyword());
			Page<WebBoard> result = repo.findAll(repo.makePredicate(vo.getType(), vo.getKeyword()), page);
			
			log.info("" + page);
			log.info("" + result);
			//model.addAttribute("result", result);
			model.addAttribute("result", new PageMaker(result));
	}
	
//	boards/register.html 호출
	@GetMapping("/register")
	public void registerGET(@ModelAttribute("vo") WebBoard vo) {
		log.info("registerGET....");
		vo.setTitle("샘플 게시물 제목입니다.");
		vo.setContent("샘플 게시물 내용입니다.");
		vo.setWriter("user00");
	}
	
//	저장하자마자 post로 간다
	@PostMapping("/register")
	public String registerPost(@ModelAttribute("vo") WebBoard vo, RedirectAttributes rttr) {
		log.info("registerGET....");
		log.info(" " + vo);
		repo.save(vo);
//		저장된 값을 출력하기위함. // addFlashAttribute : url에 붙지 않고 1회성, 새로 고침을 해도 데이터 소멸 
//		rttr의 값이 list.html의 Jquery에 간다.
		rttr.addFlashAttribute("msg", "success");
		return "redirect:/boards/list";
	}
}

WebBoardRepository.java interface

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

//	java8.0 : 디폴트 메소드 -> 오버라이딩 하지 않으면 이 메소드 사용
	public default Predicate makePredicate(String type, String keyword) {
		BooleanBuilder builder = new BooleanBuilder();
		QWebBoard board = QWebBoard.webBoard;
//		bno > 0 조건 //gt는 그레이트덴 ~ 보다 크다
		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;
		
	}
}

templates

하위 boards

llist

<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>
	
	<!--register.html 을 호출-->
	<div class="panel-body pull-right">
	<h3><a class="label label-default" th:href="@{register}">Register</a></h3>
	</div>
	
	<div class="panel-body">
<!--		<p style="color:blue">[[${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 영역-->
				<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>

		<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 class="page-item" 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">
	
	$(window).load(function(){
			var msg = [[${msg}]];
			if(msg =='success') {
				alert("정상적으로 처리되었습니다.");
				var stateObj = { msg: "" };
			}
		});
	
		$(document).ready(function() {
			
			var formObj = $("#f1");
			$(".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();
			});
			
		});
	</script>
</th:block>

templates

 

layout

하위 layout1

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

실행시키는 코드

WebBoardRepositoryTests

package com.bit;

import java.util.stream.IntStream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.annotation.Commit;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import com.bit.domain.WebBoard;
import com.bit.persistence.WebBoardRepository;

import lombok.extern.java.Log;

@ExtendWith(SpringExtension.class)
@SpringBootTest
@Log
@Commit
public class WebBoardRepositoryTests {
	
	@Autowired
	WebBoardRepository repo;
	
	@Test
	public void insertBoardDummies() {
		IntStream.range(0,  300).forEach(i->{
			WebBoard board = new WebBoard();
			board.setTitle("Sample Board Title " + i);
			board.setContent("Content Sample ..." + i + " of Board ");
			board.setWriter("user0"+(i%10));
			repo.save(board);
		});
	}
	
	@Test
	public void testList1() {
		Pageable paging = PageRequest.of(1, 20, Direction.DESC, "bno");
		Page<WebBoard> result = repo.findAll(repo.makePredicate(null, null), paging);
		log.info("PAGE:" + result.getPageable());
		log.info("----------------------------");
		log.info("PageNumber : " + result.getPageable().getPageNumber());
		log.info("TotalPages : " + result.getTotalPages());
		log.info("" + result.getPageable());
		log.info("----------------------------");
		result.getContent().forEach(board->log.info("" + board));
	}

}

자동생성된 코드

QWebBoard.java 자동생성된 코드

package com.bit.domain;

import static com.querydsl.core.types.PathMetadataFactory.*;

import com.querydsl.core.types.dsl.*;

import com.querydsl.core.types.PathMetadata;
import javax.annotation.Generated;
import com.querydsl.core.types.Path;


/**
 * QWebBoard is a Querydsl query type for WebBoard
 */
@Generated("com.querydsl.codegen.EntitySerializer")
public class QWebBoard extends EntityPathBase<WebBoard> {

    private static final long serialVersionUID = 2007671598L;

    public static final QWebBoard webBoard = new QWebBoard("webBoard");

    public final NumberPath<Long> bno = createNumber("bno", Long.class);

    public final StringPath content = createString("content");

    public final DateTimePath<java.sql.Timestamp> regdate = createDateTime("regdate", java.sql.Timestamp.class);

    public final StringPath title = createString("title");

    public final DateTimePath<java.sql.Timestamp> updatedate = createDateTime("updatedate", java.sql.Timestamp.class);

    public final StringPath writer = createString("writer");

    public QWebBoard(String variable) {
        super(WebBoard.class, forVariable(variable));
    }

    public QWebBoard(Path<? extends WebBoard> path) {
        super(path.getType(), path.getMetadata());
    }

    public QWebBoard(PathMetadata metadata) {
        super(WebBoard.class, metadata);
    }

}

현재상황

728x90
반응형

'Spring > Spring framework' 카테고리의 다른 글

Spring message properties  (0) 2022.01.07
Spring MVC Request life cycle  (0) 2021.12.24
인터셉터  (0) 2021.08.19
boot05  (0) 2021.08.11
thymeleaf  (0) 2021.08.10
JPA  (0) 2021.08.09