-
스프링부트+JPA 블로그 프로젝트 07 게시판스프링/스프링부트+JPA - 블로그 2021. 8. 31. 19:43
유투버 '데어프로그래밍'님 강의 참조
▲ 글쓰기
- 컨트롤러 세팅
@Controller public class BoardController { @GetMapping("/board/saveForm") public String saveForm() { return "board/saveForm"; }
- header.jsp에 주소 세팅
<c:otherwise> <ul class="navbar-nav"> <li class="nav-item"><a class="nav-link" href="/board/saveForm">글쓰기</a></li> <li class="nav-item"><a class="nav-link" href="/user/updateForm">회원정보</a></li> <li class="nav-item"><a class="nav-link" href="/logout">로그아웃</a></li> </ul> </c:otherwise>
- board를 위한 jsp 폴더 및 saveForm 세팅
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="../layout/header.jsp"%> <div class="container"> <form> <div class="form-group"> <label for="title">Title:</label> <input type="text" class="form-control" placeholder="Enter title" id="title"> </div> <div class="form-group"> <label for="comment">Content:</label> <textarea class="form-control summernote" rows="5" id="content"></textarea> </div> </form> <button id="btn-save" class="btn btn-primary">글쓰기 완료</button> </div> <script> $('.summernote').summernote({ placeholder : '내용', tabsize : 2, height : 300 }); </script> <script src="/js/board.js"></script> <%@ include file="../layout/footer.jsp"%>
- 디자인은 섬머노트! 헤더에 링크와 스크립트 삽입
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote-bs4.min.js"></script>
굿! - 글쓰기 완료 버튼작동을 위한 board.js 와 ajax 세팅
let index = { init:function(){ $("#btn-save").on("click",()=>{ //function(){} 대신 ()=>{}를 써서 전역변수 this를 바인딩 this.save(); }); }, save:function(){ //this.save()의 save //alert("user.js의 save 함수 호출됨"); let data = { title:$("#title").val(), content:$("#content").val() }; //console.log(data); $.ajax({ //ajax 기본 호출은 비동이 //오브젝트가 들어오는 곳 //통신 수행 type:"post", url:"/api/board", data:JSON.stringify(data),//HTTP BODY DATA contentType:"application/json; charset=utf-8", //body데이터의 타입 dataType:"json" //서버에서 받을 데이터 형식, 즉 json으로 던지고 서버를위해 자동 파싱 = JSON->JS }).done(function(resp){ //위의 데이터가 js로 바뀌고 파라미터로 사용 가능 //통신이 정상이면 done alert("글쓰기 완료"); //alert(resp) console.log(resp); location.href="/"; }).fail(function(error){ //통신이 비정상이면 fail alert(JSON.stringify(error)); }); }, } index.init();
- BoardApiController 세팅
@RestController public class BoardApiController { @Autowired private BoardService boardService; //글쓰기 진행 @PostMapping("/api/board") public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal) { boardService.insert(board, principal.getUser()); return new ResponseDto<Integer>(HttpStatus.OK.value(),1); } }
- BoardRepository 세팅
public interface BoardRepository extends JpaRepository<Board, Integer>{}
- BoardService 세팅
@Service public class BoardService { @Autowired private BoardRepository boardRepository; //글쓰기 @Transactional public void insert(Board board, User user) { board.setCount(0); // 데이터를 직접 넣고싶으면 .set board.setUser(user); boardRepository.save(board); } }
☞ 글쓰기 세팅은 여기까지이며, 현재 시큐리티가 걸려있기때문에 글쓰기까지의 프로세스를 정리하자면 ↓
- 로그인 성공 후 글쓰기를 BoardCotroller saveForm() 요청
- save()의 saveForm.jsp 가 불러지고 거기서 title/content 아이디값을 들고 글쓰기 완료 버튼을 누름
- 두개의 값이 board.js로 가서 ajax를 탐 -> 먼저 '/api/board'를 타게 됨
- title/content를 들고 BoardApiController의 save()를 탐, 거기서 title/contnet + 포린키로 묶인 user값을 받음
- 글쓰기를 하게되면 Service의 insert()함수가 실행되고 조회수/user정보가 레파지토리로 넘겨짐
- 레파지토리가 .save(board)를 진행 -> title/content/user정보를 DB에 전송
- 정상적으로 되면 똑같이 return 1 이 되고 게시판 페이지로 이동
이미지도 잘 들어간다 ▲ 글 목록 띄우기
- 일반 BoardController에서 서비스를 연결하여 데이터를 들고 메인페이지에 뿌려야 한다
@Controller public class BoardController { @Autowired private BoardService boardService; @GetMapping({"","/"}) public String index(Model model) { //데이터를 들고 오는 Model model.addAttribute("boards", boardService.list()); return "index"; }
- Service 세팅
//글목록 들고오기 public List<Board> list() { return boardRepository.findAll(); }
- index.jsp에 데이터 뿌리기 (EL표현식으로 model로 들고오는 boards의 데이터를 뿌리기)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="layout/header.jsp"%> <div class="container"> <c:forEach var="board" items="${boards}"> <div class="card m-2"> <div class="card-body"> <h4 class="card-title">${board.title}</h4> <a href="#" class="btn btn-primary">Detail</a> </div> </div> </c:forEach> </div> <%@ include file="layout/footer.jsp"%>
- model.attribute로 세팅된 데이터는 request정보라고 생각하면되고 getter처럼 불러와진다.
굿! - 페이징으로 글 목록 뿌리기
- BoardController에 메인페이지를 다루는 함수에 페이지어블 클래스 추가
@GetMapping({"","/"}) public String index(Model model, @PageableDefault(size=3,sort="id",direction = Sort.Direction.DESC) Pageable pageable) { //데이터를 들고 오는 Model model.addAttribute("boards", boardService.list(pageable)); return "index"; }
- 서비스에도 마찬가지로 페이지어블로 바꿔야함
//글목록 들고오기 @Transactional(readOnly=true) public Page<Board> list(Pageable pageable) { return boardRepository.findAll(pageable); }
- index.jsp에서 boards.content로 받아야 오류없이 페이징이 된다.
<c:forEach var="board" items="${boards.content}">
굿! - Bootstrap의 페이징 버튼을 index.jsp에 추가
<ul class="pagination justify-content-center"> <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li> <li class="page-item"><a class="page-link" href="#">Next</a></li>
- 이전/다음 버튼을 구분하는 링크 걸기
<ul class="pagination justify-content-center"> <c:choose> <c:when test="${boards.first}"> <li class="page-item disabled"><a class="page-link" href="?page=${boards.number-1}">Previous</a></li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="?page=${boards.number-1}">Previous</a></li> </c:otherwise> </c:choose> <c:choose> <c:when test="${boards.last}"> <li class="page-item disabled"><a class="page-link" href="?page=${boards.number+1}">Next</a></li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="?page=${boards.number+1}">Next</a></li> </c:otherwise> </c:choose> </ul>
Pageable이 들고있는 데이터를 가지고 boards.으로 뿌릴 수 있다는게 중요 하다. ▲ 글 상세보기
- index.jsp에서 url 설정
<a href="/board/${board.id}" class="btn btn-primary">Detail</a>
- BoardController
//상세보기 @GetMapping("/board/{id}") public String findById(@PathVariable int id, Model model) { model.addAttribute("board",boardService.detail(id)); return "board/detail"; }
- BoardService
//글 상세보기 @Transactional(readOnly=true) public Board detail(int id) { return boardRepository.findById(id) .orElseThrow(()->{ return new IllegalArgumentException("상세보기 실패!"); }); }
- detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="../layout/header.jsp"%> <div class="container"> <button class="btn btn-secondary" onclick="history.back()">돌아가기</button> <c:if test="${board.user.id==principal.user.id}"> <button id="btn-delete" class="btn btn-danger">삭제</button> <a href="/board/${board.id}/updateForm" class="btn btn-warning">수정</a> </c:if> <br> <br> <div class="form-group"> 글 번호: <span id="id"><i>${board.id} </i></span> 글 작성자: <span><i>${board.user.username} </i></span> </div> <br> <div class="form-group"> <h3>${board.title}</h3> </div> <hr/> <div class="form-group"> <h3>${board.content}</h3> </div> </div> <script src="/js/board.js"></script> <%@ include file="../layout/footer.jsp"%>
- 작성자의 경우 Board모델에 이미 User 전체 객체를 EAGER로 받아오기때문에 저렇게 받아진다!
굿! - 상세보기도 잘되고 돌아가기도 잘되는걸 볼 수 있다.
▲글 삭제하기
- board.js에서 삭제 기능 추가
let index = { init:function(){ .. $("#btn-delete").on("click",()=>{ //function(){} 대신 ()=>{}를 써서 전역변수 this를 바인딩 this.deletebyId(); }); }, .. deletebyId:function(){ var id = $("#id").text(); $.ajax({ type:"delete", url:"/api/board/"+id, }).done(function(resp){ //통신이 정상이면 done alert("삭제 완료"); location.href="/"; }).fail(function(error){ //통신이 비정상이면 fail alert(JSON.stringify(error)); }); }, } index.init();
-> value값이 아닌 일반 text 아이디 값을 받아와야 찾아서 삭제된다.
- BoardApiController
//글 삭제 요청 @DeleteMapping("/api/board/{id}") public ResponseDto<Integer> deleteById(@PathVariable int id){ boardService.deleteById(id); return new ResponseDto<Integer>(HttpStatus.OK.value(),1); }
* 명심하자 보드 아이디를 받아와야 db에서 그 아이디를 찾아서 삭제 한다.
- BoardService
@Transactional //글 삭제하기 public void delete(int id) { boardRepository.deleteById(id); }
굿! - 삭제또한 잘 처리 된다. 중간번호들이 삭제된걸 볼 수 있다
♣ 작성자와 동일한 사람만 삭제/수정되도록 세팅하자
- header.jsp 시큐리티태그가 들어가있기때문에 적용이 된다. 아래처럼 코드 추가하자
<c:if test="${board.user.id==principal.user.id}"> <button id="btn-delete" class="btn btn-danger">삭제</button> <button id="btn-update" class="btn btn-warning">수정</button> </c:if>
굿! ▲ 글 수정하기
- BoardController
@GetMapping("/board/{id}/updateForm") public String updateForm(@PathVariable int id, Model model) { model.addAttribute("board", boardService.detail(id)); return "board/updateForm"; }
- updateForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="../layout/header.jsp"%> <div class="container"> <form> <input type="hidden" id="id" value="${board.id}"/> <div class="form-group"> <input value="${board.title}" type="text" class="form-control" placeholder="Enter title" id="title"> </div> <div class="form-group"> <textarea class="form-control summernote" rows="5" id="content">${board.content}</textarea> </div> </form> <button id="btn-update" class="btn btn-primary">글 수정 완료</button> </div> <script> $('.summernote').summernote({ placeholder : '내용', tabsize : 2, height : 300 }); </script> <script src="/js/board.js"></script> <%@ include file="../layout/footer.jsp"%>
-> 아이디값이 있어야 어느 게시판을 불러올지 db에서 거르기때문에 hidden으로 처리하자
- board.js
let index = { .. $("#btn-update").on("click",()=>{ //function(){} 대신 ()=>{}를 써서 전역변수 this를 바인딩 this.update(); }); }, .. update:function(){ let id = $("#id").val(); let data = { title:$("#title").val(), content:$("#content").val() }; $.ajax({ //ajax 기본 호출은 비동기 //오브젝트가 들어오는 곳 //통신 수행 type:"put", url:"/api/board/"+id, data:JSON.stringify(data),//HTTP BODY DATA contentType:"application/json; charset=utf-8", //body데이터의 타입 dataType:"json" //서버에서 받을 데이터 형식, 즉 json으로 던지고 서버를위해 자동 파싱 = JSON->JS }).done(function(resp){ //위의 데이터가 js로 바뀌고 파라미터로 사용 가능 //통신이 정상이면 done alert("글 수정 완료"); location.href="/"; }).fail(function(error){ //통신이 비정상이면 fail alert(JSON.stringify(error)); }); }, } index.init();
- BoardApiController
//글 수정 요청 @PutMapping("/api/board/{id}") public ResponseDto<Integer> update(@PathVariable int id, @RequestBody Board board){ boardService.update(id, board); return new ResponseDto<Integer>(HttpStatus.OK.value(),1); }
- BoardService
//글 수정하기 @Transactional public void update(int id, Board requestBoard) { Board board = boardRepository.findById(id) .orElseThrow(()->{ return new IllegalArgumentException("글 찾기 실패!"); }); //영속화 board.setTitle(requestBoard.getTitle()); board.setContent(requestBoard.getContent()); //Transactional로 영속성 더티체킹! }
굿! * 자바스크립트가 반영이 빨리 안되면 캐시비우기로 한번 비우고 시작하자
'스프링 > 스프링부트+JPA - 블로그' 카테고리의 다른 글
스프링부트+JPA 블로그 프로젝트 09 회원정보 수정(시큐리티) (0) 2021.09.01 스프링부트+JPA 블로그 프로젝트 08 스프링 작동 원리 복습 (0) 2021.09.01 스프링부트+JPA 블로그 프로젝트 06 로그인(트랜잭션/시큐리티) (0) 2021.08.30 스프링부트+JPA 블로그 프로젝트 05 회원가입 개념(DTO/Ajax) (0) 2021.08.30 스프링부트+JPA 블로그 프로젝트 04 부트와 JPA의 필수 개념 (JSON/영속성/어노테이션 그리고 CRUD 테스트) (0) 2021.08.28