ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ch5, 6 - N:1(다대일 관계) 처리하기(+ @RestController와 JSON 처리)
    SpringBoot 2024. 1. 2. 20:10

     (책 - 코드로 배우는 스프링 부트 웹 프로젝트)

    https://github.com/uqualid/SBW-ex5-board

     

    GitHub - uqualid/SBW-ex5-board

    Contribute to uqualid/SBW-ex5-board development by creating an account on GitHub.

    github.com

    간단하게 프로젝트에 대해 언급하자면 entity 세 개 -  Board(방명록), Reply(댓글), Member(회원) - 로 구성된

    방명록 및 댓글 CRUD 기능이 구현되어 있는 프로젝트이다 

    이중 @ManyToOne과 @RestController(JSON 처리) 관련 내용만 정리해 볼 것이다 

     

     

    1. @ManyToOne

    ERD

    세 엔티티 간의 관계는 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은 

    https://zeddios.tistory.com/90

    중괄호 내에 데이터를 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 기능을 구현한다

    댓글