ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] JPA의 이해
    JPA 2024. 7. 28. 01:22

    [JPA 동작방식]

     

    1. JPQL(Java Persistence Query Language) 작성

     

    2. JPQL → SQL 변환 (Native Query로의 변환)

    JPA가 내부에서 JPQL을 분석해서 네가 사용하는 진짜 데이터베이스에 맞는 SQL을 만듦

     

    3. DB에 Native SQL 전송 후 결과 받아옴

     


    [그럼 여기서 JPQL이란?]

     

    JPQL은 Entity 기준 (클래스/필드 기준)

    SQL은 Table 기준 (DB 테이블/컬럼 기준)

     

    JPQL은 DB를 몰라도 작성 가능하게 설계된 거고,
    JPA가 알아서 테이블 구조, 관계를 기반으로 native SQL을 만들어주는 거야.

     

    그래서 이 JPQL을 어떤 Native Query로 작성해서 CRUD 작업을 했는지 확인하려는게 아래 코드이다.

    application.yml에 작성하는 jpa 관련 설정 정보이다.

    spring:
      jpa:
        show-sql: true # JDBC가 최종적으로 날리는 SQL 쿼리 콘솔 출력
        properties:
          hibernate:
            format_sql: true # SQL 보기 좋게 정렬
            use_sql_comments: true # 쿼리 주석 추가 (원본 JPQL 알려줌)

     


    JPA는 객체지향적인 DB 설계를 위해 나온 기술이다

    따라서 다음과 같이 Entity Class를 짜면 해당 클래스 그대로 데이터베이스에 테이블을 만들어준다

     

    @Entity
    @Getter
    @Setter
    public class Puppy {
        @Id
        private String puppyId;
    
        @Column(columnDefinition = "varchar(225)", nullable = false)
        private String password;
    
        @Column(columnDefinition = "varchar(255)", nullable = false)
        private String nickName;
    
        @Column(columnDefinition = "varchar(225)", nullable = false)
        private String name;
    
        @Column(columnDefinition = "DATE", nullable = false)
        private LocalDate birth;
    
        @Column(columnDefinition = "varchar(225)", nullable = false)
        private String phoneNumber;
    
        @Column(columnDefinition = "varchar(255)", nullable = false)
        private String address;
    
        @Column(columnDefinition = "varchar(255)", nullable = false)
        private String detailAddress;
    
        @Embedded
        private Location location;
    
        // 연관관계 매핑
        @OneToMany(mappedBy = "puppy", cascade = CascadeType.ALL)
        private List<Food> foodList = new ArrayList<>();
    
        // 연관관계 매핑
        @OneToMany(mappedBy = "puppy", cascade = CascadeType.ALL)
        private List<FavoriteHost> favoriteHostList = new ArrayList<>();
    }

     

    하지만 @OneToMany 로 정의된 List 들은 테이블에 들어가지 않는다

     

    왜그럴까?

     

    @OneToMany 와 같은 JPA의 연관관계를 명시하는 어노테이션은

    JOIN 해야하는 외래키를 명시하는 어노테이션에 불과하기 때문이다

     

    List도 JOIN을 통해 접근하기 위한 멤버변수 read-only 에 불과하기 때문이다!

     

    @Entity
    @Getter
    @Setter
    public class Food {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long foodId;
    
        @ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinColumn(name="hostId")
        private Host host;
    
        @ManyToOne(fetch=FetchType.LAZY, cascade = CascadeType.ALL)
        @JoinColumn(name="puppyId")
        private Puppy puppy;
    
        @Column(columnDefinition = "varchar(225)", nullable = false)
        private String menu;
    
        @Column(nullable = false)
        private LocalDateTime time; // 식사시간
    
        @Column(columnDefinition = "varchar(225)", nullable = false)
        private String imageURL;
    
        @Column(nullable = false)
        @Enumerated(EnumType.STRING)
        private FoodStatus status; // 매칭상태
    }

     

    Food Entity를 보자

     

    여기도 실제로 데이터베이스에 host와 puppy로 들어가지 않는다

    @JoinColumn으로 정의된 hostId와 puppyId가 멤버변수로 들어간다

     

    왜냐하면 Host host 변수와 Puppy puppy 변수는

    JPA의 의의인 코드를 통해 객체지향적인 DB 접근방식을 선택했기 때문에

    그냥 코드로써 편하게 연관관계를 정립할 수 있게 만든 것이기 때문이다!

     


    여기서 문제가 되는, 헷갈리는 부분이

    그럼 새로운 Food를 추가해야할때 어느 쪽에 추가해야하나 이다

     

    Puppy의 FoodList에 추가해야하나?

    아니면 Food 에 새로운 Entity로 추가해야하나?

     

    직관적으로 보면 Puppy의 foodList에 추가하는 것이 맞다

    하지만 이렇게 하면 안된다

     

    저번에 배운 연관관계의 주인(외래키를 들고 있는 놈)이 Food 이기 때문에

    Food 테이블에 새로운 Food Entity 자체를 추가하는 방식을 써야한다

     

    그럼 foodList는 왜 있는 것일까?

     

    여기서 JPA의 의의를 알 수 있다

     

    JPA를 사용하지않으면 Puppy와 Food 테이블의 조인을 통해 해당 Puppy와 연관된 Food 엔티티를 다 불러와야한다

     

    하지만 JPA는 @OneToMany 어노테이션을 통해 JOIN을 알아서 해주기 때문에

    그냥 puppy.getFoodList로 해당 Puppy와 연관된 Food 엔티티를 불러 올 수 있다!!

     

    따라서 정리하자면

     

    CRUD에서

     

    그냥 ReadOnly 작업만 필요한 READ 쪽은 연관관계의 주인이 아닌쪽에서도 해도 된다

     

    하지만 CUD 즉 CREATE UPDATE DELETE

    실제로 테이블의 변화를 일으키는 작업들은

    무조건 연관관계의 주인인 쪽에서 해줘야한다!!

     


     

    이렇게 readonly 작업은 JPA가 지원해주는 기술을 통해

    멤버변수로 둘 수 있었던 foodList를 바로 사용하면 된다

     

    하지만 Puppy와 연관된 새로운 Food를 추가하는 작업은

    연관관계의 주인쪽에서 해줘야한다

    Puppy는 Food와의 관계에서 연관관계의 주인이 아니므로

    Puppy 쪽에서 새로운 Food 엔티티를 추가해주면 안된다!!

     

     

    Puppy 가 해당 행위의 주체이기 때문에 이렇게 PuppyService 쪽에서 시작을 하는데

    Puppy Entity 를 추출하는 작업만 puppyService를 통해서 하고

    실제로 Food Entity를 추가하는 작업은 foodService에게 위임해야한다

     

    연관관계의 주인이 Food이기 때문이다!!

     

    'JPA' 카테고리의 다른 글

    [JPA] cascadeType  (0) 2024.09.05
    [JPA] @JoinColumn의 이해  (0) 2024.09.01
    연관관계 편의 메서드  (0) 2024.07.08
    @mappedBy의 이해  (0) 2024.07.08
    단방향 양방향 연관관계의 이해  (0) 2024.07.08