ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [자바/Java] 자바 JPA 알아보기
    Java 2021. 7. 25. 23:37

    ORM (Object-Relational Mapping / 객체 관계 매핑) 이란?

    객체와 DB의 데이터와 관계를 맺어주는 것을 ORM이라고 한다. 객체를 통해 간접적으로 DB의 데이터를 다룸으로써,

    보다 더 객체지향적으로 프로그램을 설계하고 DB의 데이터를 쉽게 다룰 수 있다. ORM은 결국 객체와 디비 데이터의 연결관계를

    맺어주는 것이므로 최종 동작하는 것은 쿼리문이다.

     

    JPA란?

    자바 ORM 기술에 대한 표준명세, 자바에서 제공하는 ORM API이다. 자바 어플리케이션에서 관계형 데이터베이스를

    사용하는 방식을 정의한 인터페이스이며 인터페이스이기 때문에 실제로 동작하는 것은 아니며 대중적으로 JPA를 구현한

    Hibernate를 구현체로 널리 사용한다.

     

    영속성 컨텍스트 (Persistence Context)

    프로그램을 종료하여도 사라지지 않는 데이터의 특성을 영속성이라 한다. JPA를 통해 Entity를 다루려면 Entity가 영속성 컨텍스트에

    존재해야된다. Entity Manager가 Entity를 영속성 컨텍스트에 저장하거나 조회하며 Entity를 관리하고, Entity를 영구 저장하는 환경(컨테이너)을 영속성 컨텍스트라 한다. 

     

    영속 상태

    • 영속 (managed) : EntityManager가 Entity를 관리하는 상태 (영속성 컨텍스트에 Entity가 저장된 상태)
    • 준영속 (detached) : EntityManager가 Entity를 관리하지 않는 상태 (영속성 컨텍스트에 저장되었다가 분리된 상태)
    • 비영속 (new) : DB와 관련이 없는 순수 Java Object 상태 (영속성 컨텍스트와 관련이 없는 상태)

     

    영속 상태 조작 방법

    • @Trasient : 해당 필드는 영속화에서 제외됨, 영속성 컨텍스트에서 관리를 하지 않는다.
    • EntityManager.persist(target) : target을 영속화(managed 상태)한다.
    • EntityManager.detach(target) : target을 준영속화(detached 상태)한다.
    • EntityManager.merge(target) : 준영속화(detached 상태)된 target을 다시 영속화(managed 상태) 한다.
    • EntityManager.remove(target) : target을 삭제한다.

     

    Entity의 기본속성 (Annotation)

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @RequiredArgsConstructor
    @EqualsAndHashCode
    @Builder
    @Entity
    @Table(name = "user")
    public class User {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY) //생성 방법을 db에 맡기겠다.
        private Long id;
    
        @NonNull
        private String name;
    
        @NonNull
        private String email;
    
        @Enumerated(value = EnumType.STRING)
        private Gender gender;
    
        @Column(updatable = false)
        private LocalDateTime createdAt;
    
        @Column(insertable = false)
        private LocalDateTime updatedAt;
    
        @Transient
        private String testData;
    
    }
    • @Entity : JPA에서 관리하고 있는 Entity 객체임을 정의한다.
      • Entity로 지정시 PK가 반드시 필요함 -> @Id Annotation으로 지정 가능하다.
    • @GeneratedValue : 개발자가 아닌 JPA에게 키 값 생성 역할을 넘긴다.
      • GenerationType 옵션 (ex : @GeneratedValue(strategy = GenerationType.IDENTITY)) :
        • TABLE : DB종류에 상관없이 ID 값을 관리하는 별도의 테이블을 생성하고 그 테이블에서 추출해서 사용한다.
        • SEQUENCE : SEQUENCE를 사용하는 DB에서 활용 가능 / SEQUENCE에서 키 값을 받음 (Oracle, PostgreSQL, H2 등)
        • IDENTITY : 사용되는 DB의 기능 활용 (ex : MySQL의 AUTO_INCREMENT)
        • AUTO : 각 DB에 적합한 값을 자동으로 넘겨준다. (Default)
    • @Column : 각 컬럼마다 다양한 옵션 지정이 가능하다.
    • @Transient : 해당 필드는 영속성 처리에서 제외된다. -> DB의 레코드로써 사용하는 것이 아닌 자바의 객체로 사용하겠다는 의미이다.
    • @Enumerated(value = EnumType.STRING) : Enum 객체 사용시 Odinal (서순)이 DB에 저장되거나 하는 문제를 방지한다.

     

    Entity의 Listener

    Entity의 select, insert, update, delete 시점에 원하는 기능을 적용할 수 있게 해준다.

    • @PrePersist : Insert 전에 동작한다.
    • @PreUpdate : Update 전에 동작한다.
    • @PreRemove : Delete 전에 동작한다.
    • @PostPersist : Insert 후에 동작한다.
    • @PostUpdate : Update 후에 동작한다.
    • @PostRemove : Delete 후에 동작한다.
    • @PostLoad : Select 후에 동작한다.

     

    연관 관계 정의 규칙

    • 방향 : 단방향, 양방향 (객체 참조)
      • DB 테이블은 외래키 하나로 양쪽 테이블 조인이 가능하지만 JPA는 그렇지 않다.
      • 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계, 두 객체 모드가 각각 참조용 필드를 갖고 참조하면 양방향 관계이다.
    • 연관 관계의 주인 : 양방향일 때, 연관 관계에서 관리 주체
    • 다중성 : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:N)

     

    JPA 연관 관계 Annotation

    @Entity
    @NoArgsConstructor
    @Data
    @ToString(callSuper = true)
    @EqualsAndHashCode(callSuper = true)
    public class Book extends BaseEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        private String category;
    
        private Long authorId;
    
        //private Long publisherId;
    
        @OneToOne(mappedBy = "book")
        @ToString.Exclude
        private BookReviewInfo bookReviewInfo;
    
        @OneToMany
        @JoinColumn(name = "book_id")
        @ToString.Exclude
        private List<Review> reviews;
    
        @ManyToOne
        @ToString.Exclude
        private Publisher publisher;
    }
    • @OneToOne : 1:1 관계를 정의한다.
      • 위 코드 @OneToOne에서 옵션으로 (mappedBy = {해당 클래스})가 정의되어 있는데, 해당 옵션이 의미하는 바는 Book와 BookReviewInfo끼리 1:1 관계를 형성하되, Book에 Foreign Key를 정의하지 않겠다는 의미이다.
    • @OneToMany : 1:N 관계를 정의한다.
      • 위 코드에서 @JoinColumn(name = "book_id")가 의미하는 것은 우선 1:N 관계 중 1이 Book을 의미하고 N은 Review를 의미하는데, 1:N 관계를 표현하기 위해 RDB 상에서는 Review 측에 Foreign Key가 필요한데 JPA에서 해당 Foreign Key를 "book_id"로 네이밍하고 연관 관계를 짓겠다는 의미이다.
    • @ManyToOne : N:1 관계를 정의한다.
    • @ManyToMany : N:N 관계를 정의한다.
      • 기본적으로 N:N 관계는 실무에서 잘 사용하지 않는다.

     

    영속성 전이 (Cascade)

    @Entity
    @NoArgsConstructor
    @Data
    @ToString(callSuper = true)
    @EqualsAndHashCode(callSuper = true)
    public class Book extends BaseEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String name;
    
        private String category;
    
        private Long authorId;
    
        //private Long publisherId;
    
        @OneToOne(mappedBy = "book")
        @ToString.Exclude
        private BookReviewInfo bookReviewInfo;
    
        @OneToMany
        @JoinColumn(name = "book_id")
        @ToString.Exclude
        private List<Review> reviews;
    
        @ManyToOne(cascade = CascadeType.PERSIST)
        @ToString.Exclude
        private Publisher publisher;
    }
    • ex) 위 코드에서 @ManyToOne(cascade = CascadeType.PERSIST) 의미하는 바는 Book 입장에서 Publisher가 Persist(Insert)될 떄 영속성을 전이시키겠다는 의미, 다른 동작(update, delete 등)에서는 동작을 안 한다.

     

    OrphanRemoval (고아 제거)

    연관관계가 없는 Entity를 삭제한다. ex) Book dto에 필드 변수로 Publisher publisher이 있다.

    Book과 Publisher는 서로 연관을 지을 수 있다.

    • @OneToMany(orphanRemoval = true)
    @OneToMany(orphanRemoval = true)
    @JoinColumn(name = "publisher_id")
    @ToString.Exclude
    private List<Book> books = new ArrayList<>();
    • setter에 null을 주입하면?
    // .. jpa를 통해 Book Entity에 Publisher가 주입되어 있다고 가정
    book.setPublisher(null);
    bookRepository.save(book);

    위와 같이 하면 연관관계는 제거 되지만, publisher는 존재한다.

     

    Embedded, Embeddable

    DTO를 가독성 있게, 자바스럽게 분리 할 수 있게 해주는 기능

    @Entity
    @...
    public class User extends BaseEntity {
    
      @Id...
    
      @NonNull
      private String name;
    
      @NonNull
      private String email;
      
      @Embedded
      @AttributeOverrides({
              @AttributeOverride(name = "city", column = @Column(name = "home_city")),
              @AttributeOverride(name = "district", column = @Column(name = "home_district")),
              @AttributeOverride(name = "detail", column = @Column(name = "home_address_detail")),
              @AttributeOverride(name = "zipCode", column = @Column(name = "home_zip_code"))
      })
      private Address homeAddress;
    
      @Embedded
      @AttributeOverrides({
              @AttributeOverride(name = "city", column = @Column(name = "company_city")),
              @AttributeOverride(name = "district", column = @Column(name = "company_district")),
              @AttributeOverride(name = "detail", column = @Column(name = "company_address_detail")),
              @AttributeOverride(name = "zipCode", column = @Column(name = "company_zip_code"))
      })
      private Address companyAddress;
    }
    //******************************************************************************************
    @Embeddable
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Address {
    
      private String city;
    
      private String district;
    
      @Column(name = "address_detail")
      private String detail;
    
      private String zipCode;
      
    }

    위와 같이 Emded 기능을 사용하면 JPA가 아래와 같은 ddl을 정의한다.
    좀 더 자바스럽게(객체지향스럽게) DB를 컨트롤 할 수 있고, 가독성이 높다.

    create table user (
      id bigint not null auto_increment,
      created_at datetime,
      updated_at datetime,
      company_city varchar(255),
      company_address_detail varchar(255),
      company_district varchar(255),
      company_zip_code varchar(255),
      email varchar(255),
      gender varchar(255),
      home_city varchar(255),
      home_address_detail varchar(255),
      home_district varchar(255),
      home_zip_code varchar(255),
      name varchar(255),
      primary key (id)
    )engine=InnoDB

     

    댓글