jusung-c
으 하기싫어
jusung-c
  • 공부 기록 (96)
    • Spring (42)
      • Spring 기초 이론 (8)
      • Spring 핵심 원리 - 기본 (9)
      • Blog 만들기 with SpringBoot (25)
    • JAVA (7)
      • Java 문법 (2)
      • 객체지향의 사실과 오해 (5)
    • Algorithm (47)
      • 자료구조 (3)
      • 완전탐색 (22)
      • 정렬 (4)
      • 이분탐색 (12)
      • 투 포인터 (4)
hELLO · Designed By 정상우.
jusung-c

으 하기싫어

게시판
Spring/Blog 만들기 with SpringBoot

게시판

2022. 7. 25. 02:51

글작성하기

 

로그인 후 네비게이션 바의 글쓰기 눌렀을 때 /board/saveForm으로 이동하도록 설정

BoardController.java

@Controller
public class BoardController {
	
	...
	
	// USER 권한 필요
	@GetMapping("/board/saveForm")
	public String saveFrom() {
		return "board/saveForm";
	}
}

 

board/saveForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다.  -->
<%@ include file="../layout/header.jsp"%>

<div class="container">
	글쓰기 화면
</div>

<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다.  -->
<%@ include file="../layout/footer.jsp"%>

 

 

잘 연결된다.

 

summer note 적용하기

https://summernote.org/getting-started/

 

<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/saveForm.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다.  -->
<%@ 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>

	<!-- JSON으로 요청할 거라서 form 밖으로 뺀다. -->
	<button id="btn-save" class="btn btn-primary">글쓰기완료</button>
	
</div>

<!-- summer note script -->
<script>
	$('.summernote').summernote({
		tabsize : 2,
		height : 300
	});
</script>

<script src="/js/board.js"></script>

<!-- 현재 board 파일 안이므로 ../로 올라가줘야 한다.  -->
<%@ include file="../layout/footer.jsp"%>

 

js/board.js

let index = {
    init: function() {
        $("#btn-save").on("click", () => {
            this.save();
        });
    },

    save: function(){
        let data = {
			title: $("#title").val(),
			content: $("#content").val()
		};
		
		$.ajax({
			type: "POST",
			url: "/api/board",
			data: JSON.stringify(data),
			contentType: "application/json; charset=utf-8",
			dataType: "json"
			
		}).done(function(resp){
			alert("글쓰기가 완료되었습니다.");
			location.href="/";
			
		}).fail(function(error){
			alert(JSON.stringify(error));
		});
    },
}

index.init();

 

BoardApiController.java

@RestController
public class BoardApiController {
	
	@Autowired
	private BoardService boardService;
	
	@PostMapping("/api/board")		// board와 user 정보가 필요하다!
	public ResponseDto<Integer> save(@RequestBody Board board, @AuthenticationPrincipal PrincipalDetail principal) {
		// user 정보를 얻어오기 위해 PrincipalDetail에 @Getter를 적어줬다.
		boardService.글쓰기(board, principal.getUser());
		
		return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
	}
	
}

 

BoardRepository.java

public interface BoardRepository extends JpaRepository<Board, Integer>{
	
}

 

BoardService.java

@Service
public class BoardService {

	@Autowired
	private BoardRepository boardRepository;
	
	@Transactional
	public void 글쓰기(Board board, User user) { // title, content
		board.setCount(0);
		board.setUser(user);
		boardRepository.save(board);
	}
}

 

 

 

 

흐름 정리

1. 글쓰기 요청

2. BoardController.java의 @GetMapping("/board/saveForm")로 매핑

3. saveForm에서 글쓰기 완료 버튼을 누르면

4. boards.js에서 title과 content 데이터를 JSON으로 /api/board에 보냄

5. BoardApiController에서 @PostMapping("/api/board")매핑

6. board에는 title, content, user 정보가 필요하므로 user 가져온 후

7. boardService의 글쓰기 함수 호출해서 DB에 저장

 

 

 

글 목록 보기

이제 작성한 글들을 메인 페이지에서 볼 수 있도록 해보자. 

 

BoardController에서 "/"로 갈 때 데이터를 가져가야 한다. 스프링에서는 데이터를 가져갈 때 모델이 필요하다. 

BoardController.java

@Controller
public class BoardController {
	
	@Autowired
	private BoardService boardService;
	
	@GetMapping({"", "/"})
	public String index(Model model) {
		model.addAttributes("boards", boardService.글목록());
		
		return "index";
	}
    
	...
}

 

BoardService.java

@Service
public class BoardService {

	...
	
	public List<Board> 글목록() { 
		return boardRepository.findAll();
	}
}

 

이제 index.jsp에서 가져온 글목록을 뿌려주기만 하면 된다!

index.jsp

<%@ 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">상세 보기</a>
			</div>
		</div>
	</c:forEach>


</div>

<%@ include file="layout/footer.jsp"%>

 

흐름 정리

1. "/" 요청

2. boardService.글목록() 으로 글 전부를 다 긁어온다.

3. 긁어온 정보를 "boards"라는 이름으로 index에 넘긴다.

4. index는 {boards}를 받아서 뿌려준다.

 

 

글목록 페이징하기

예전에 페이징을 위해 @PageableDefault를 사용해봤었는데 똑같이 적용한다.

 

BoardController.java

@Controller
public class BoardController {
	
	@Autowired
	private BoardService boardService;
	
	@GetMapping({"", "/"})
	public String index(Model model, @PageableDefault(size=3, sort="id", direction = Sort.Direction.DESC) Pageable pageable) {
		model.addAttribute("boards", boardService.글목록(pageable));
		
		return "index";
	}
	
	...
}

 

BoardService.java

@Service
public class BoardService {
	...
	
    @Transactional(readOnly = true)
	public Page<Board> 글목록(Pageable pageable) { 
		return boardRepository.findAll(pageable);
	}
}

 

 

index.js

<%@ 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.content}">
		<div class="card m-2">
			<div class="card-body">
				<h4 class="card-title">${board.title }</h4>
				<a href="#" class="btn btn-primary">상세 보기</a>
			</div>
		</div>
	</c:forEach>

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

</div>

<%@ include file="layout/footer.jsp"%>

 

 

 

글 상세보기

상세보기를 누르면 http://localhost:8000/board/{글의 id값} 링크로 이동하도록 수정

index.js

...
<div class="container">

	<c:forEach var="board" items="${boards.content}">
		<div class="card m-2">
			<div class="card-body">
				<h4 class="card-title">${board.title }</h4>
				<a href="/board/${board.id}" class="btn btn-primary">상세 보기</a>
			</div>
		</div>
	</c:forEach>

	...

</div>

...

 

상세보기 링크로 이동하면 컨트롤러에서 id값을 매개변수로 boardService의 글 상세보기를 실행해서 리턴 받은 값을 "board"라는 이름으로 board/detail에 넘겨준다.

BoardController.java

@Controller
public class BoardController {
	
	...
	
	@GetMapping("/board/{id}")
	public String findById(@PathVariable int id, Model model) {
		model.addAttribute("board", boardService.글상세보기(id));
		return "board/detail";
	}
	
	...
}

 

BoardService.java

@Service
public class BoardService {

	...
	
    @Transactional(readOnly = true)
	public Board 글상세보기(int id) {
		return boardRepository.findById(id)
				.orElseThrow(()->{
					return new IllegalArgumentException("글 상세보기 실패 : 아이디를 찾을 수 없습니다.");
				});
	}
}

 

전달받은 "board"로 title과 content 정보를 뿌려준다.

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>
	<button id="btn-update" class="btn btn-warning">수정</button>
	<button id="btn-delete" class="btn btn-danger">삭제</button>
	<br/><br/>
	<div>
		<h3>${board.title}</h3>
	</div>
	<hr />
	<div>
		<div>${board.content}</div>
	</div>
	<hr />
	
</div>

<script src="/js/board.js"></script>
<%@ include file="../layout/footer.jsp"%>

 

 

 

글 삭제하기

일단 상세보기에서 글 번호와 작성자를 보여주자. board는 EAGER로 user 정보를 다 들고오니까 board.user.username으로 작성자 정보를 가져올 수 있다.

detail.jsp

...
<div class="container">

	...
	<c:if test="${board.user.id == principal.user.id }">
		<button id="btn-update" class="btn btn-warning">수정</button>
		<button id="btn-delete" class="btn btn-danger">삭제</button>
	</c:if>
	<br /><br />
	<div>
		글 번호 : <span id="id"><i>${board.id} </i></span>
		작성자 : <span><i>${board.user.username}  </i></span>
	</div>
	...

</div>
...

 

 

삭제를 위해서는 id값이 필요하다.  $("#id").text(); 로 id 값을 찾아온 후 url에 붙여준다.

board.js

let index = {
	init: function() {
		...
		$("#btn-delete").on("click", () => {
			this.deleteById();
		});
	},

	...
	
	deleteById: function() {
		var id = $("#id").text();
		
		$.ajax({
			type: "DELETE",
			url: "/api/board/"+id,
			dataType: "json"

		}).done(function(resp) {
			alert("삭제가 완료되었습니다.")
			location.href = "/";

		}).fail(function(error) {
			alert(JSON.stringify(error));
		});
	},
}

index.init();

 

BoardApiController에서 @DeleteMapping으로 "/api/board/{id}" url이 매핑되면 id를 추출해서 삭제시킨다.

BoardApiController.java

@RestController
public class BoardApiController {
	...
	
	@DeleteMapping("/api/board/{id}")
	public ResponseDto<Integer> deleteById(@PathVariable int id){
		boardService.글삭제하기(id);
		return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
	}
}

 

BoardService.java

public class BoardService {
	...
	
	@Transactional
	public void 글삭제하기(int id) {
		boardRepository.deleteById(id);
	}
}

 

 

글 수정하기

수정 버튼을 누르면 각 글의 updateForm으로 이동해서 수정할 수 있도록 한다.

detail.jsp

	<a href="/board/${board.id}/updateForm" class="btn btn-warning">수정</button>

 

BoardController.java

	@GetMapping("/board/{id}/updateForm")
	public String updateForm(@PathVariable int id, Model model) {
		model.addAttribute("board", boardService.글상세보기(id));
		return "board/updateForm";
	}

 

수정을 하기 위해선 title, content 값 뿐만 아니라 id값도 있어야 하므로 hidden 타입으로 적어둔다.

board/updateForm

...

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

...

 

입력된 id, title, content 값을 들고와서 Ajax를 작동시킨다.

js/board.js

let index = {
	init: function() {
		...
        
		$("#btn-update").on("click", () => {
			this.update();
		});
	},

	...
	
	update: function() {
		let id = $("#id").val();
		
		let data = {
			title: $("#title").val(),
			content: $("#content").val()
		};

		$.ajax({
			type: "PUT",
			url: "/api/board/"+id,
			data: JSON.stringify(data),
			contentType: "application/json; charset=utf-8",
			dataType: "json"

		}).done(function(resp) {
			alert("글수정이 완료되었습니다.");
			location.href = "/";

		}).fail(function(error) {
			alert(JSON.stringify(error));
		});
	},
	
}

index.init();

 

BoardApiController.java

	@PutMapping("/api/board/{id}")
	public ResponseDto<Integer> update(@PathVariable int id, @RequestBody Board board) {
		boardService.글수정하기(id, board);
		return new ResponseDto<Integer>(HttpStatus.OK.value(), 1);
	}

 

BoardService.java

	@Transactional
	public void 글수정하기(int id, Board requestBoard) {
		Board board = boardRepository.findById(id)
				.orElseThrow(()->{
					return new IllegalArgumentException("글 찾기 실패 : 아이디를 찾을 수 없습니다.");
				}); // 영속화 완료
		
		board.setTitle(requestBoard.getTitle());
		board.setContent(requestBoard.getContent());
		// 해당 함수로 종료시에(Service가 종료될 때) 트랜잭션이 종료된다. 이때 더티체킹이 들어간다. -> 자동 업데이트. DB flush!!
	}

 

 

 

최종 정리

 

 

 

 

 

 

출처 : https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9

저작자표시 비영리 변경금지 (새창열림)

'Spring > Blog 만들기 with SpringBoot' 카테고리의 다른 글

카카오 로그인 구현  (0) 2022.08.21
회원 수정  (0) 2022.07.25
비밀번호 해쉬 & 시큐리티 로그인  (0) 2022.07.24
스프링부트 Spring Security  (0) 2022.07.14
전통적인 방식의 로그인  (0) 2022.07.09
    'Spring/Blog 만들기 with SpringBoot' 카테고리의 다른 글
    • 카카오 로그인 구현
    • 회원 수정
    • 비밀번호 해쉬 & 시큐리티 로그인
    • 스프링부트 Spring Security
    jusung-c
    jusung-c

    티스토리툴바