-
ch5, 6 - N:1(다대일 관계) 처리하기(+ @RestController와 JSON 처리)SpringBoot 2024. 1. 2. 20:10
(책 - 코드로 배우는 스프링 부트 웹 프로젝트)
https://github.com/uqualid/SBW-ex5-board
간단하게 프로젝트에 대해 언급하자면 entity 세 개 - Board(방명록), Reply(댓글), Member(회원) - 로 구성된
방명록 및 댓글 CRUD 기능이 구현되어 있는 프로젝트이다
이중 @ManyToOne과 @RestController(JSON 처리) 관련 내용만 정리해 볼 것이다
1. @ManyToOne
세 엔티티 간의 관계는 board와 외의 2개 엔터티가 N:1의 관계로 단방향 참조의 형태이다
PK 참조 시에 그렇지 않은 경우와의 차이는 엔터티를 구성하는 코드에서 두드러지는데,
@ManyToOne(N:1) 어노테이션을 사용해서 참조 대상의 엔터티를 선언해 주어야 한다는 것이다
package com.example.board.entity; import jakarta.persistence.*; import lombok.*; @Entity @Builder @AllArgsConstructor @NoArgsConstructor @Getter @ToString(exclude = "board") // 해당 클래스 모든 변수 출력, exclude 습관적 사용 필요 public class Reply extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long rno; private String text; private String replyer; @ManyToOne(fetch = FetchType.LAZY) private Board board; // 연관관계 지정 }
@ManyToOne 어노테이션 뒤에 설정하는 fetch = 은 특정 엔터티를 조회할 때,
JPA에서 연관관계의 엔터티를 어떻게 가져올 것인지에 대한 모드를 지정해 주는 것이다
- Eager Loading(즉시 로딩) - 연관관계를 가진 모든 엔터티를 같이 로딩, 성능 저하 우려
- Lazy Loading(지연 로딩) - 무조건적인 조인 x -> 빠른 속도 처리
but 필요 시에 매번 쿼리를 실행하기 때문에 연관관계가 복잡한 경우 불리
이러한 이유로 보편적으로는 지연 로딩을 주로 사용하지만 필요의 경우에 따라 즉시 로딩을 하는 경우도 있다고 한다
2. @RestController(JSON 처리)
해당 프로젝트의 경우 reply(답글) 관련 CRUD 기능 구현에서 JSON + @RestController를 사용하였다
먼저 JSON은
중괄호 내에 데이터를 name-value의 쌍과 따옴표로 구분하는 파싱 형태를 가진다
(프론트 공부한 지가 오래 돼서 그냥 그렇다더라. 하는 중... 그렇댑니다)
컨트롤러의 경우 @Controller와 @RestController 어노테이션이 있고 둘의 쓰임은 거의 비슷하나 @RestController가 JSON 데이터 형태의 객체 데이터를 반환하는 데에 사용되는 어노테이션이라고 한다
(차이점에 관한 내용은 해당 블로그를 참고함)
@RestController는 모든 메서드의 리턴 타입을 기본적으로 JSON을 사용하고, HTTP의 상태 코드 등을 같이 전달하는 ResponseEntity 객체를 사용한다
ReplyController
package com.example.board.controller; // import 생략 @RestController @RequestMapping("/replies/") @Log4j2 @RequiredArgsConstructor public class ReplyController { private final ReplyService replyService; // 자동 주입 - final @GetMapping(value = "/board/{bno}", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<List<ReplyDTO>> getListBoard(@PathVariable("bno") Long bno){ log.info("bno: " + bno); return new ResponseEntity<>(replyService.getList(bno), HttpStatus.OK); } @PostMapping("") public ResponseEntity<Long> register(@RequestBody ReplyDTO replyDTO){ log.info(replyDTO); Long rno = replyService.register(replyDTO); return new ResponseEntity<>(rno, HttpStatus.OK); } @DeleteMapping("/{rno}") public ResponseEntity<String> remove(@PathVariable("rno") Long rno){ log.info("RNO: " + rno); replyService.remove(rno); return new ResponseEntity<>("success", HttpStatus.OK); } @PutMapping("/{rno}") public ResponseEntity<String> modify(@RequestBody ReplyDTO replyDTO){ log.info(replyDTO); replyService.modify(replyDTO); return new ResponseEntity<>("success", HttpStatus.OK); } }
read.html 일부
//특정한 게시글의 댓글을 처리하는 함수 function loadJSONData() { $.getJSON('/replies/board/'+bno, function(arr){ console.log(arr); var str =""; $('.replyCount').html(" Reply Count " + arr.length); $.each(arr, function(idx, reply){ console.log(reply); str += ' <div class="card-body" data-rno="'+reply.rno+'"><b>'+reply.rno +'</b>'; str += ' <h5 class="card-title">'+reply.text+'</h5>'; str += ' <h6 class="card-subtitle mb-2 text-muted">'+reply.replyer+'</h6>'; str += ' <p class="card-text">'+ formatTime(reply.regDate) +'</p>'; str += ' </div>'; }) listGroup.html(str); }); } $(".replyCount").click(function(){ loadJSONData(); })//end click //모달 창 var modal = $('.modal'); $(".addReply").click(function () { modal.modal('show'); //댓글 입력하는 부분 초기화 시키기 $('input[name="replyText"]').val(''); $('input[name="replyer"]').val(''); $(".modal-footer .btn").hide(); //모달 내의 모든 버튼을 안 보이도록 $(".replySave, .replyClose").show(); //필요한 버튼들만 보이도록 }); $(".replySave").click(function() { var reply = { bno: bno, text: $('input[name="replyText"]').val(), replyer: $('input[name="replyer"]').val() } console.log(reply); $.ajax({ url: '/replies/', method: 'post', data: JSON.stringify(reply), contentType: 'application/json; charset=utf-8', dataType: 'json', success: function(data){ console.log(data); var newRno = parseInt(data); alert(newRno +"번 댓글이 등록되었습니다.") modal.modal('hide'); loadJSONData(); } }) }); $('.replyList').on("click", ".card-body", function(){ var rno = $(this).data("rno"); $("input[name='replyText']").val( $(this).find('.card-title').html()); $("input[name='replyer']").val( $(this).find('.card-subtitle').html()); $("input[name='rno']").val(rno); $(".modal-footer .btn").hide(); $(".replyRemove, .replyModify, .replyClose").show(); modal.modal('show'); }); $(".replyRemove").on("click", function(){ var rno = $("input[name='rno']").val(); //모달 창에 보이는 댓글 번호 hidden처리되어 있음 $.ajax({ url: '/replies/' + rno, method: 'delete', success: function(result){ console.log("result: " + result); if(result ==='success'){ alert("댓글이 삭제되었습니다"); modal.modal('hide'); loadJSONData(); } } }) }); $(".replyModify").click(function() { var rno = $("input[name='rno']").val(); var reply = { rno: rno, bno: bno, text: $('input[name="replyText"]').val(), replyer: $('input[name="replyer"]').val() } console.log(reply); $.ajax({ url: '/replies/' + rno, method: 'put', data: JSON.stringify(reply), contentType: 'application/json; charset=utf-8', success: function(result){ console.log("RESULT: " + result); if(result ==='success'){ alert("댓글이 수정되었습니다"); modal.modal('hide'); loadJSONData(); } } }); });
loadJSONData를 호출해서 화면을 갱신하고, Ajax 처리를 통해 @RestController로 CRUD 기능을 구현한다
'SpringBoot' 카테고리의 다른 글
Enum 열거형 선언(엔터티 내, 별도 엔터티) (0) 2024.03.02 Part 4(Ch 7, 8, 9) - M:N(다대다) 관계와 파일 업로드 처리 (0) 2024.01.15 ch4 - 방명록 프로젝트 톺아보기 (0) 2023.12.28 ch2 - Spring Data JPA (0) 2023.12.21 [5주차] 댓글 CRUD 구현 (0) 2023.12.04