반응형
목차
보통 셀프조인은 카테고리처럼 1차, 2차, 3차.... 무한정으로 늘어날 수 있을 때 사용되는데,
하나의 테이블로 모든 관계를 정의할 수 있어서 유용하게 쓰인다.
셀프조인 형태로 구현한 카테고리 엔티티와 메뉴 엔티티를 맵핑하고 Json을 내려주는 과정에서
어떻게 순환참조를 피할 수 있을까??
예시데이터
카테고리
메뉴
내가 원하는 응답값
카테고리
{
"message": "카테고리 정보를 조회하였습니다.",
"data": {
"id": 2,
"categoryCode": "soup_stews",
"categoryName": "찜_탕_찌개",
"parent": {
"id": 1,
"categoryCode": "koreanfood",
"categoryName": "한식",
"parent": null,
"childList": null
},
"childList": [
{
"id": 3,
"categoryCode": "stew",
"categoryName": "찌개",
"parent": null,
"childList": null
},
{
"id": 4,
"categoryCode": "steamed",
"categoryName": "찜",
"parent": null,
"childList": null
}
]
}
}
- 찌개에서도 parent를 타고 들어갈 수 있지만 depth를 지정하여 조회하고 싶다
- parent와 child가 필요할때만 값을 조회하고 싶다
메뉴
카테고리별로 조회할 경우
더보기
{
"message": "메뉴 정보 리스트를 조회하였습니다.",
"data": {
"content": [
{
"createdDateTime": "2022-11-09 00:32:19",
"modifiedDateTime": "2022-11-09 00:32:19",
"id": 1,
"menuCode": 111,
"menuName": "김치찌개",
"description": "김치찌개입니다.",
"categoryCode": "stew",
"categoryId": 3,
"categoryName": "찌개"
},
{
"createdDateTime": "2022-11-09 00:32:41",
"modifiedDateTime": "2022-11-09 00:32:41",
"id": 2,
"menuCode": 112,
"menuName": "된장찌개",
"description": "된장찌개입니다.",
"categoryCode": "stew",
"categoryId": 3,
"categoryName": "찌개"
}
],
"currentPage": 1,
"totalPages": 1,
"pageSize": 10,
"firstPage": true,
"lastPage": true
}
}
그냥 모든 메뉴 조회할 경우
더보기
{
"message": "메뉴 정보 리스트를 조회하였습니다.",
"data": {
"content": [
{
"createdDateTime": "2022-11-09 00:32:19",
"modifiedDateTime": "2022-11-09 00:32:19",
"id": 1,
"menuCode": 111,
"menuName": "김치찌개",
"description": "김치찌개입니다.",
"categoryCode": "stew",
"categoryId": 3,
"categoryName": "찌개"
},
{
"createdDateTime": "2022-11-09 00:32:41",
"modifiedDateTime": "2022-11-09 00:32:41",
"id": 2,
"menuCode": 112,
"menuName": "된장찌개",
"description": "된장찌개입니다.",
"categoryCode": "stew",
"categoryId": 3,
"categoryName": "찌개"
},
{
"createdDateTime": "2022-11-09 00:33:01",
"modifiedDateTime": "2022-11-09 00:33:01",
"id": 3,
"menuCode": 113,
"menuName": "잔치국수",
"description": "잔치국수입니다.",
"categoryCode": "noodle",
"categoryId": 5,
"categoryName": "국수"
},
{
"createdDateTime": "2022-11-09 00:33:10",
"modifiedDateTime": "2022-11-09 00:33:10",
"id": 4,
"menuCode": 114,
"menuName": "칼국수",
"description": "칼국수입니다.",
"categoryCode": "noodle",
"categoryId": 5,
"categoryName": "국수"
},
{
"createdDateTime": "2022-11-09 00:34:02",
"modifiedDateTime": "2022-11-09 00:34:02",
"id": 5,
"menuCode": 115,
"menuName": "아구찜",
"description": "아구찜입니다.",
"categoryCode": "steamed",
"categoryId": 4,
"categoryName": "찜"
},
{
"createdDateTime": "2022-11-09 00:34:12",
"modifiedDateTime": "2022-11-09 00:34:12",
"id": 6,
"menuCode": 116,
"menuName": "찜닭",
"description": "찜닭입니다.",
"categoryCode": "steamed",
"categoryId": 4,
"categoryName": "찜"
}
],
"currentPage": 1,
"totalPages": 1,
"pageSize": 10,
"firstPage": true,
"lastPage": true
}
}
- 카테고리별로 조회할수도 있어야하지만 그냥 전체를 조회할 수도 있어야한다.
- 그러기 위해선 객체마다 카테고리 정보가 있어야한다.
내가 생각한 아이디어 첫번째 : 카테고리 순환참조 방지는 DTO로
public class CategoryDto {
.....중략
/**
* 순환참조를 방지하기 위해 따로 정의한다
*/
@Data
private static class JsonRes {
private Long id;
private String categoryCode;
private String categoryName;
private JsonRes parent; //JsonRes이다. 주의!
private List<JsonRes> childList; //JsonRes이다. 주의!
public JsonRes(Category category, boolean getParent, boolean getChild, Integer depth) {
this.id = category.getId();
this.categoryCode = category.getCategoryCode();
this.categoryName = category.getCategoryName();
if(depth > 0){ //depth가 0이 될때까지만 조회한다.
if(getParent){ //getParent가 true일때만 부모를 조회한다
Category parent = category.getParentCategory();
if(parent != null){ //부모를 조회할 때, 부모가 있을때만 JSON을 생성한다.
this.parent = new JsonRes(parent, getParent, false, depth-1);
//child는 아래에서 따로 구할거니까 false로 고정한다.
}
}
if(getChild){ //getChild가 true일때만 부모를 조회한다
List<Category> childList = category.getChildCategoryList();
if(childList != null){ //자식을 조회할 때, 자식이 있을때만 JSON을 생성한다.
this.childList = childList.stream()
.map(child -> new JsonRes(child, getParent, true, depth-1))
.collect(Collectors.toList());
//자식 전부 구할거니까 true로 고정한다.
}
}
}
}
}
.....중략
}
내가 생각한 아이디어 두번째 : 메뉴-카테고리 맵핑 시 카테고리 내용은 getter로
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "menu")
@DynamicUpdate
public class Menu extends BaseTimeEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", columnDefinition = "bigint comment '아이디'")
private Long id;
@Column(name = "menu_code", columnDefinition = "bigint comment '메뉴코드'")
private Long menuCode;
@Column(name = "menu_name", columnDefinition = "varchar(255) comment '메뉴명'")
private String menuName;
@Column(name = "description", columnDefinition = "text comment '메뉴설명'")
private String description;
@JsonIgnore
@Getter(AccessLevel.PRIVATE) //category 자체 getter 접근 막는다
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id", columnDefinition = "bigint comment '카테고리 아이디'")
private Category category;
@Column(name = "category_code", columnDefinition = "varchar(255) comment '카테고리 코드'")
private String categoryCode;
@Transient //DB에 저장하지 않고 사용하는 컬럼
private Long categoryId;
@Transient //DB에 저장하지 않고 사용하는 컬럼
private String categoryName;
@Builder
public Menu(Long id, Long menuCode, String menuName, String description, Category category) {
this.id = id;
this.menuCode = menuCode;
this.menuName = menuName;
this.description = description;
this.category = category;
this.categoryId = category.getId();
this.categoryCode = category.getCategoryCode();
this.categoryName = category.getCategoryName();
}
public String getCategoryCode() {
return this.category.getCategoryCode(); //getter 정의
}
public Long getCategoryId() {
return this.category.getId(); //getter 정의
}
public String getCategoryName() {
return this.category.getCategoryName(); //getter 정의
}
}
전체 코드
테스트
카테고리 조회 depth 확인
카테고리 getChild, getParent 동작 확인
메뉴 카테고리별 조회 / 일반조회
이것저것 해보다가 이런 방법도 있겠구나~~ 해서 정리해봤다!
반응형
'Backend' 카테고리의 다른 글
[SpringBoot] Jpa Connection Minimum-Idle 설정하지 말자... 에러 후기 (0) | 2022.12.04 |
---|---|
[SpringBoot] Apache Poi를 이용한 엑셀다운로드는 SXSSF를 쓰자..! (0) | 2022.12.03 |
Server-Sent Event (SSE)란? feat Node.js (0) | 2022.09.20 |
[SpringBoot] Apache Poi를 이용한 엑셀 다운로드 구현 (0) | 2022.08.20 |
[Spring Boot] FeignClient와 ExceptionHandler | FeignClient의 응답값 그대로 반환하기 (1) | 2022.07.29 |