[JPA] 순환참조 오류와 해결방법

Yeoonnii·2022년 10월 27일
0

JPA

목록 보기
6/9

오류

ItemItemImg를 여러개 가지는 1:N 구조를 갖고있다

다음과 같은 로직을 구성하는데 오류가 발생했다

  1. 카테고리에 해당하는 Item목록 List<Item> 조회하기
  2. 조회된 IteminoList<Long>형태로 생성
  3. 생성된 List<Long> 으로 ItemImg 조회
    (해당하는 물품의 물품이미지 조회)
  4. 3에서 조회된 ItemImg 오름차순으로 조회후 0번째에 해당하는 iimageno를 가져와 imgurl 생성하기
  5. 1의 List<Item>을 불러와 Imageurl 넣어주기 setImageurl
    @GetMapping(value = "/selectitem.json")
    public Map<String, Object> selectItemGET(
            HttpServletRequest request,
            @RequestParam(name = "page", defaultValue = "1") int page,
            @RequestParam(name = "icateno", defaultValue = "1") Long icateno) {
        Map<String, Object> map = new HashMap<>();
        try {
            // 페이지네이션 설정(0부터, 1페이지에 출력될 개수)
            PageRequest pageRequest = PageRequest.of(page - 1, 12);

            // 카테고리에 해당하는 아이템 가져오기 => 최신 등록순 정렬 + 페이지네이션
            List<Item> itemList = ItemRepository.findByItemcateIcatenoOrderByRegdateDesc(icateno, pageRequest);

            // ino를 long타입의 list로 만듬
            List<Long> inoList = new ArrayList<>();
            for (int i = 0; i < itemList.size(); i++) {
                inoList.add(itemList.get(i).getIno());
            }

            for (int i = 0; i < inoList.size(); i++) {
                // 조회된 물품에 해당하는 이미지
                List<ItemImg> itemImg = ItemImgRepository.findByItem_inoOrderByIimagenoAsc(inoList.get(i));
                if (itemImg.size() != 0) { // 이미지가 있는경우
                    System.out.println("0번째 itemimage");
                    System.out.println(itemImg.get(0));

                    // Imageurl 생성
                    String setImageurl = request.getContextPath()
                            + "/api/item/image?ino=" + itemImg.get(0).getIimageno();

                    // i번째 itemList에 Imageurl 넣어주기
                    itemList.get(i).setImageurl(setImageurl);

                } else { // 이미지가 없는경우
                    ItemDTO itemDTO = new ItemDTO();
                    itemDTO.setIno(itemList.get(i).getIno());
                    itemDTO.setName(itemList.get(i).getName());
                    itemDTO.setPrice(itemList.get(i).getPrice());
                    itemDTO.setRegdate(itemList.get(i).getRegdate());

                    itemDtolist.add(itemDTO);
                }
            }

            map.put("status", 200);
            map.put("itemList", itemList);
        } catch (Exception e) {
            map.put("status", -1);
            map.put("result", e.getMessage());
        }
        return map;
    }

순환참조 발생

해결

생성한 Imageurl를 넣어줄때 조회된 List<Item>를 다시 가져와 사용하지 않고
ItemDTO를 새로 생성하여 필요한 컬럼만 담아 List<ItemDTO>형태로 반환해주었다

    @GetMapping(value = "/selectitem.json")
    public Map<String, Object> selectItemGET(
            HttpServletRequest request,
            @RequestParam(name = "page", defaultValue = "1") int page,
            @RequestParam(name = "icateno", defaultValue = "1") Long icateno) {
        Map<String, Object> map = new HashMap<>();
        try {
            // 페이지네이션 설정(0부터, 1페이지에 출력될 개수)
            PageRequest pageRequest = PageRequest.of(page - 1, 12);

            // 카테고리에 해당하는 아이템 가져오기 => 최신 등록순 정렬 + 페이지네이션
            List<Item> itemList = ItemRepository.findByItemcateIcatenoOrderByRegdateDesc(icateno, pageRequest);
            System.out.println("========================물품목록itemList==================");
            System.out.println(itemList);

            // ino를 long타입의 list로 만듬
            List<Long> inoList = new ArrayList<>();
            for (int i = 0; i < itemList.size(); i++) {
                inoList.add(itemList.get(i).getIno());
            }
            System.out.println("========================물품목록itemList==================");
            System.out.println(inoList);

            List<ItemDTO> itemDtolist = new ArrayList<>();
            for (int i = 0; i < inoList.size(); i++) {
                System.out.println("*******inoList.get(i)******");
                System.out.println(inoList.get(i));
                // 조회된 물품에 해당하는 이미지
                List<ItemImg> itemImg = ItemImgRepository.findByItem_inoOrderByIimagenoAsc(inoList.get(i));
                System.out.println("*************itemImg.size()**********");
                System.out.println(itemImg.size());
                if (itemImg.size() != 0) { // 이미지가 있는경우
                    System.out.println("0번째 itemimage");
                    System.out.println(itemImg.get(0));

                    // itemList.get(i).getImageurl() = request.getContextPath() +
                    // "/api/item/image?ino=" + obj.getIimageno();

                    // Imageurl 생성
                    String Imageurl = request.getContextPath()
                            + "/api/item/image?ino=" + itemImg.get(0).getIimageno();

                    //DTO 만들어서 넣어주기
                    ItemDTO itemDTO = new ItemDTO();
                    itemDTO.setIno(itemList.get(i).getIno());
                    itemDTO.setName(itemList.get(i).getName());
                    itemDTO.setPrice(itemList.get(i).getPrice());
                    itemDTO.setRegdate(itemList.get(i).getRegdate());
                    itemDTO.setImageurl(Imageurl);

                    itemDtolist.add(itemDTO);

                } else {
                    ItemDTO itemDTO = new ItemDTO();
                    itemDTO.setIno(itemList.get(i).getIno());
                    itemDTO.setName(itemList.get(i).getName());
                    itemDTO.setPrice(itemList.get(i).getPrice());
                    itemDTO.setRegdate(itemList.get(i).getRegdate());

                    itemDtolist.add(itemDTO);
                }
            }

            map.put("status", 200);
            // map.put("itemList", itemList);
            map.put("itemDtolist", itemDtolist);
        } catch (Exception e) {
            map.put("status", -1);
            map.put("result", e.getMessage());
        }
        return map;
    }

순환참조 해결방법

  1. @JsonIgnore
    : 이 어노테이션을 붙이면 json 데이터에 해당 프로퍼티는 null로 들어가게 된다.
    즉, 데이터에 아예 포함이 안되게 된다.

  2. @JsonManagedReference@JsonBackReference
    : 이 두개의 어노테이션이야 말로 순환참조를 방어하기 위한 Annotation이다.
    부모 클래스에 @JsonManagedReference를, 자식 클래스측 에 @JsonBackReference 어노테이션을 추가해주면 된다.

  3. DTO 사용
    : 위와 같은 상황이 발생하게 된 주 원인은 양방향 매핑이기도 하지만,
    더 정확하게는 entity 자체를 response로 리턴했기 때문에 순환참조가 발생하는 것이다
    entity 자체를 return하지 않고, dto 객체를 만들어 필요한 데이터만 옮겨 담아 client로 리턴하면
    순환참조와 관련된 문제는 애초에 방어할 수 있다.

  4. 맵핑 재설정
    : 사람마다 다르지만 양방향 맵핑이 꼭 필요한지 다시 한번 생각해볼 필요가 있다.
    만약 양쪽에서 접근할 필요가 없다면 단방향 맵핑을 하면 자연스레 순환참조가 해결된다.

참조링크 - [JPA] 순환참조와 해결방법

0개의 댓글