본문 바로가기
목차
Spring/Spring boot

스프링부트 강좌 67강(블로그 프로젝트) - 댓글 목록 뿌리기

by 지각생 2022. 1. 26.
728x90

출처:

https://www.youtube.com/watch?v=DjnMw07U1VI&list=PL93mKxaRDidECgjOBjPgI3Dyo8ka6Ilqm&index=69 


무한 참조시 이렇게 된다.

 

board 정보를 가져올때

46,49행 user, replys정보를 가져오게 된다.

 

user를 가져올땐 연관관계 없으니 문제 없지만

 

replys정보를 가져올 땐 문제가 있다.

여기서 return할 때 

//jacson 라이브러리 발동(오브젝트를 json으로 리턴)

=>모델의 getter를 호출

getId, getTitle, getContent, getCount, getUser, getReplys, getCreateDate

가 호출된다는 거다.

 

여기서 getReplys를 호출하면

getId, getContent, getBoard, getUser, getReply, getCreateDate

를 호출하는데 여기서 getBoard가 또 호출되니

 

getReplys와 getBoard가 서로 계속 호출하며 무한참조를 하게 된다.

 

해결하려면

 

최초 board 진행되고 reply에서 board를 호출하지 않도록 하자

이거 배워볼거임

50행 작성해주면

여기서 getBoard 호출이 일어나지 않게 해준다.

 

나는 저렇게 작성해도 무한참조가 발생하는데

 

나중에 다시 다른방법들로도 해본다니 넘어가봐야겠다.

 

<%@ 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 }">
		<a href="/board/${board.id}/updateForm" class="btn btn-warning">수정</a>
		<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>
	<br>
	<div>
		<h3>${board.title}</h3>
	</div>
	<hr />
	<div>
		<div>${board.content}</div>
	</div>
	<hr />

	<button id="btn-save" class="btn btn-primary">글 저장</button>


	<div class="card">
		<div class="card-body">
			<textarea class="form-control" row="1"></textarea>
		</div>
		<div class="card-footer">
			<button class="btn btn-primary">등록</button>
		</div>
	</div>
	<br />
	<div class="card">
		<div class="card-header">댓글 리스트</div>
		<ul id="reply-box" class="list-group">
			<c:forEach var="reply" items="${board.replys}">
				<li id="reply--1" class="list-group-item d-flex justify-content-between">
					<div>${reply.content}</div>
					<div class="d-flex">
						<div class="font-italic">작성자 : ${reply.user.username} &nbsp;</div>
						<button class="badge">삭제</button>
					</div>
				</li>
			</c:forEach>
		</ul>
	</div>

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

 

package com.cosblog.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cosblog.model.Board;
import com.cosblog.model.User;
import com.cosblog.repository.BoardRepository;

//스프링이 컴포넌트 스캔을 통해서 bean에 등록을 해줌
@Service
public class BoardService {

	@Autowired
	private BoardRepository boardRepository;

	@Transactional
	public void 글쓰기(Board board, User user) {
		board.setCount(0);
		board.setUser(user);
		boardRepository.save(board);
	}
	
	@Transactional(readOnly=true)
	public Page<Board> 글목록(Pageable pageable) {
		return boardRepository.findAll(pageable);
	}
	
	@Transactional(readOnly=true)
	public Board 글상세보기(int id) {
		return boardRepository.findById(id)
				.orElseThrow(() -> {
			return new IllegalArgumentException("글 상세보기 실패:아이디를 찾을 수 없습니다.");
		});
	}

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

/*
 * 이 로그인도 사용 안 할거라 삭제
 * 
 * @Transactional(readOnly = true)//Select할 때 트랜잭션 시작, 서비스 종료시에 트랜잭션 종료(정합성)
 * public User 로그인(User user) { return
 * userRepository.findByUsernameAndPassword(user.getUsername(),
 * user.getPassword()); }
 */
package com.cosblog.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.cosblog.model.Reply;

public interface ReplyRepository extends JpaRepository<Reply, Integer>{
	
}
package com.cosblog.model;

import java.sql.Timestamp;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import org.hibernate.annotations.CreationTimestamp;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Board {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@Column(nullable = false, length = 100)
	private String title;
	
	@Lob //대용량 데이터
	private String content; //섬머노트 라이브러리<html>태그가 섞여서 디자인 됨.
	
	//@ColumnDefault("0") //조회수 강제로 넣어줄 것임.
	private int count; 
	
	@ManyToOne(fetch = FetchType.EAGER) //Many = Board, User = One
	@JoinColumn(name="userId")//DB테이블 필드값은 userId로 만들어지고 연관관계는 ManyToOne으로 만들어진다.
	private User user;//DB는 오브젝트를 저장할 수 없다. FK, 자바는 오브젝트를 저장할 수 있다.
	
	@OneToMany(mappedBy = "board", fetch = FetchType.EAGER) //Many = Board, User = One
	@JsonIgnoreProperties({"board"})
	private List<Reply> replys;//DB는 오브젝트를 저장할 수 없다. FK, 자바는 오브젝트를 저장할 수 있다.
	
	
	@CreationTimestamp
	private Timestamp createDate;
}
package com.cosblog.model;

import java.sql.Timestamp;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

import org.hibernate.annotations.CreationTimestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Entity
public class Reply {

	@Id //Primary key
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id; //시퀀스, auto_increment
	
	@Column(nullable = false,length =200)
	private String content;
	
	@ManyToOne
	@JoinColumn(name="boardId")
	private Board board;
	
	@ManyToOne
	@JoinColumn(name="userId")
	private User user;
	
	@OneToMany(mappedBy="board")//mappedBy는 연관관계의 주인이 아니다(난 FK가 아니에요) DB에 칼럼을 만들지 마세요.
	private List<Reply> reply;
	
	@CreationTimestamp
	private Timestamp createDate;
}
package com.cosblog.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

import com.cosblog.service.BoardService;

import org.springframework.data.domain.Sort;




@Controller
public class BoardController {
	
	@Autowired
	private BoardService boardService;
	
	//public String index(@AuthenticationPrincipal PrincipalDetail principal)
	
	@GetMapping({"","/"})
	public String index(Model model,@PageableDefault(size=3, sort="id", direction = Sort.Direction.DESC)Pageable pageable) {
		model.addAttribute("boards", boardService.글목록(pageable));
		return "index"; // viewResolver 작동!
	}
	
	@GetMapping("/board/{id}/updateForm")
	public String updateForm(@PathVariable int id, Model model) {
		model.addAttribute("board",boardService.글상세보기(id));
		return "board/updateForm";
	}
	
	@GetMapping("board/{id}")
	public String findById(@PathVariable int id, Model model) {
		model.addAttribute("board", boardService.글상세보기(id));
		return "board/detail";
		 
	}
	
	//USER 권한이 필요
	@GetMapping("/board/saveForm")
	public String saveForm() {
		
		return "board/saveForm";
	}
}
package com.cosblog.test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.cosblog.model.Board;
import com.cosblog.repository.BoardRepository;

@RestController
public class ReplyControllerTest {
	
	@Autowired
	private BoardRepository boardRepository;
	
	@GetMapping("/test/board/{id}")
	public Board getBoard(@PathVariable int id) {
		return boardRepository.findById(id).get();
	}
}
728x90

댓글