ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 간단하게 OneToMany 데이터 API 구현하기 - @Embeddable, @Embedded, @ElementCollection 활용하기
    Spring Boot/JPA 2023. 10. 15. 23:14

    요구사항

    • 레시피를 저장하고 조회, 삭제할 수 있는 API를 구성한다.
    • JPA 연관관계 설정 없이 OneToMany 데이터 모델링 방법을 정리한다.

    API Spec

    • 레시피 저장 api
    POST localhost:8881/api/recipe/new
    # requestBody
    {
       "name": "Fresh Mint Tea",
       "description": "Light, aromatic and refreshing beverage, ...",
       "ingredients": ["1 inch ginger root, minced", "1/2 lemon, juiced", "1/2 teaspoon manuka honey"],
       "directions": ["Place all ingredients in a mug and fill with warm water (not too hot so you keep the beneficial honey compounds in tact)", "Steep for 5-10 minutes", "Drink and enjoy"]
    }
    200 OK
    # response
    {
        "id": 1
    }
    • 레시피 조회 api
    GET localhost:8881/api/recipe/1 
    200 OK
    # response
    {
        "name": "Fresh Mint Tea",
        "description": "Light, aromatic and refreshing beverage, ...",
        "ingredients": [
            "1 inch ginger root, minced",
            "1/2 lemon, juiced",
            "1/2 teaspoon manuka honey"
        ],
        "directions": [
            "Place all ingredients in a mug and fill with warm water (not too hot so you keep the beneficial honey compounds in tact)",
            "Steep for 5-10 minutes",
            "Drink and enjoy"
        ]
    }

     

    처음 요구사항을 보았을때, 기계적으로 아래처럼 생각했다.

     

    1. Recipe, Ingredient, Direction Entity 클래스를 만들고,
    2. OneToMany로 Recipe 필드에 List<Ingredient>, List<Direction> 필드 연관관계 맺기

    3. 단방향/양방향 매핑을 고민

    4. Cascade나 한꺼번에 저장, 삭제를 신경쓴다.

     

    API는 굉장히 단순한데 이렇게까지 복잡한 과정을 거쳐야할까? 

     

    @Embeddable, @Embedded, @ElementCollection이라는 굉장히 편한 도구가 있다. 

    • 효과적으로 Persist data를 다루기 위해 등장
    • 코드를 더 깔끔하게, Data Persistence에 모듈화된 접근 방식 제공
    • Complex data structure를 개발자가 더 쉽게 다룰 수 있게 돕는다.

     

    @Embeddable, @Embedded

    • Spring Data가 부모 Entity에 자식 Entity를 Embeded하게 할 수 있다.
    • @Embeddable을 자식 Entity에 선언하고 @Embedded를 부모 Entity에 선언하면 자식 클래스가 부모의 부분으로 동작하게 할 수 있다.

    아래 위치를 표현하는 Entity가 있고 위도와 경도를 추상화하여 따로 분리하고 싶다고 가정해보자. 

    @Entity
    public class Location {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private String longitude;
        private String latitude;
    
        // getters and setters
    }
    public class Coordinates {
        private String longitude;
        private String latitude;
    
        // getters and setters
    }

    @Embeddable, @Embedded을 사용하면 아래처럼 구현 및 관리할 수 있다.

    @Embeddable
    public class Coordinates {
        private String longitude;
        private String latitude;
    
        // getters and setters
    }
    
    @Entity
    public class Location {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
    
        @Embedded
        private Coordinates coordinates;
    
        // getters and setters
    }
    
    • Spring Data를 사용하면 Hibernate가 JPA를 구현하기 때문에, @Embeddable, @Embedded 중에 하나만 선언하면 다른 한쪽은 생략가능하다.
    • 애플리케이션 단에는 Object 2개로 관리되지만 DB에 Table은 하나이다.

    @ElementCollection

    • 종속되는 컬렉션을 정의하는 방법
      • OneToMany 컬렉션을 처리하기 위한 별도 엔티티를 만들고, 관계를 구성하지 않아도 된다.
      • @ElementCollection 선언만으로 OneToMany 관계 생성이 가능하다.
      • 물론 별도의 클래스를 생성해서 Collection으로 관리되도록 하는 방식도 지원한다.
      • The @ElementCollection can be used with standard library types like String, Integer or with embeddable objects annotated as @Embeddable
    @Embeddable
    public class Rating {
        private String username;
        private int stars;
    
        // getters and setters
    }
    
    @Entity
    public class Location {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
    
        @Embedded
        private Coordinates coordinates;
    
        @ElementCollection
        private Set<String> tags;
    
        @ElementCollection
        private Set<Rating> ratings;
    
        // getters and setters
    }
    

    테이블

     

     

     

    API 구현 

    • 위에 요구했던 API 구현에 @ElementCollection을 이용하면 따로 클래스를 생성하고 연관관계를 맺는 과정들을 생략할 수 있다.
    @Entity
    @Getter
    @NoArgsConstructor
    public class Recipe {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id", nullable = false)
        private Long id;
        private String name;
        private String description;
        @ElementCollection // 기본 fetch 전략은 Lazy loading
        private List<String> ingredients;
        @ElementCollection // 기본 fetch 전략은 Lazy loading
        private List<String> directions;
    }

    아래와 같이 테이블 생성됨

    자동 생성된 테이블

     

    반응형
Designed by Tistory.