๐ŸŒฟ Spring

Spring Jpa SelfJoin ์ˆœํ™˜์ฐธ์กฐ ๋ฐฉ์ง€ํ•˜๋ฉฐ ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์™€ ๋งตํ•‘ํ•˜๊ธฐ

์—ฐ_์šฐ๋ฆฌ 2022. 11. 9. 01:43
๋ฐ˜์‘ํ˜•

๋ชฉ์ฐจ



    ๋ณดํ†ต ์…€ํ”„์กฐ์ธ์€ ์นดํ…Œ๊ณ ๋ฆฌ์ฒ˜๋Ÿผ 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 ์ •์˜
        }
    }





    ์ „์ฒด ์ฝ”๋“œ

    GitHub - lotu-us/category-menu-sample

    Contribute to lotu-us/category-menu-sample development by creating an account on GitHub.

    github.com





    ํ…Œ์ŠคํŠธ

    ์นดํ…Œ๊ณ ๋ฆฌ ์กฐํšŒ depth ํ™•์ธ


    ์นดํ…Œ๊ณ ๋ฆฌ getChild, getParent ๋™์ž‘ ํ™•์ธ




    ๋ฉ”๋‰ด ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ์กฐํšŒ / ์ผ๋ฐ˜์กฐํšŒ








    ์ด๊ฒƒ์ €๊ฒƒ ํ•ด๋ณด๋‹ค๊ฐ€ ์ด๋Ÿฐ ๋ฐฉ๋ฒ•๋„ ์žˆ๊ฒ ๊ตฌ๋‚˜~~ ํ•ด์„œ ์ •๋ฆฌํ•ด๋ดค๋‹ค!

    ๋ฐ˜์‘ํ˜•
    • ๋„ค์ด๋ฒ„ ๋ธ”๋Ÿฌ๊ทธ ๊ณต์œ ํ•˜๊ธฐ
    • ํŽ˜์ด์Šค๋ถ ๊ณต์œ ํ•˜๊ธฐ
    • ํŠธ์œ„ํ„ฐ ๊ณต์œ ํ•˜๊ธฐ
    • ๊ตฌ๊ธ€ ํ”Œ๋Ÿฌ์Šค ๊ณต์œ ํ•˜๊ธฐ
    • ์นด์นด์˜คํ†ก ๊ณต์œ ํ•˜๊ธฐ