몬그로이

ORM 본문

Organizing Docs/Java Docs

ORM

Mon Groy 2024. 6. 28. 20:00

ORM 은 DAO 또는 Mapper 를 통해서 조작하는것이 아니라

테이블을 아예 하나의 객체(Object)와 대응시켜 버립니다.

객체지향(Object) 을 관계형 데이터베이스(Relation) 에 매핑(Mapping) 한다는건

정말 많은 난관이 있습니다. 어떻게 해결했는지 알아볼까요?

 

 

상속의 문제

  • 객체 : 객체간에 멤버변수나 상속관계를 맺을 수 있다.
  • RDB : 테이블들은 상속관계가 없고 모두 독립적으로 존재한다.

해결방법 : 매핑정보에 상속정보를 넣어준다. (@OneToMany, @ManyToOne)

 

관계 문제

  • 객체 : 참조를 통해 관계를 가지며 방향을 가진다. (다대다 관계도 있음)
  • RDB : 외래키(FK)를 설정하여 Join 으로 조회시에만 참조가 가능하다. (즉, 다대다는 매핑 테이블 필요)

 해결방법 : 매핑정보에 방향정보를 넣어준다. (@JoinColumn, @MappedBy)

 

탐색 문제

  • 객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능하며 콜렉션도 순회한다.
  • RDB : 탐색시 참조하는 만큼 추가 쿼리나, Join 이 발생하여 비효율적이다.

 해결방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다.(@FetchType, fetchJoin())

 

밀도 문제

  • 객체 : 멤버 객체크기가 매우 클 수 있다.
  • RDB : 기본 데이터 타입만 존재한다.

 해결방법 : 크기가 큰 멤버 객체는 테이블을 분리하여 상속으로 처리한다. (@embedded)

 

식별성 문제

  • 객체 : 객체의 hashCode 또는 정의한 equals() 메소드를 통해 식별
  • RDB : PK 로만 식별

해결방법 : PK 를 객체 Id로 설정하고 EntityManager는 해당 값으로 객체를 식별하여 관리 한다.(@Id,@GeneratedValue )

 


영속성 4가지 상태( Entity Manager가 관)

 

① 비영속(new/transient) 

엔티티 객체가 만들어져서 아직 저장되지 않은 상태로, 영속성컨텍스트와 전혀 관계가 없는 상태

 

② 영속(managed)

엔티티가 영속성 컨텍스트에 저장되어, 영속성 컨텍스트가 관리할 수 있는 상태

 

③ 준영속(detached)

엔티티가 영속성 컨텍스트에 저장되어 있다가 분리된 상태로, 영속성컨텍스트가 더 이상 관리하지 않는 상태

 

④ 삭제(removed)

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제하겠다고 표시한 상태

 

 

new > (비영속상태) > persist(),merge() > (영속성 컨텍스트에 저장된 상태) 

> flush() > (DB에 쿼리가 전송된 상태) > commit() > (DB에 쿼리가 반영된 상태)

 

Item item = new Item(); // 1

item.setItemNm("테스트 상품");

 

EntityManager em = entityManagerFactory.createEntityManager(); // 2

EntityTransaction transaction = em.getTransaction(); // 3

 

transaction.begin();

em.persist(item); // 4-1

em.flush(item). // 4-2 (DB에 SQL 보내기/commit시 자동수행되어 생략 가능함)

transaction.commit(); // 5

 

em.close(); // 6

 

1️⃣ 영속성 컨텍스트에 담을 상품 엔티티 생성

2️⃣ 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성

3️⃣ 데이터 변경 무결성을 위해 트랜잭션 시작

4️⃣ 영속성 컨텍스트에 저장된 상태, 아직 DB에 INSERT SQL 보내기

5️⃣ 트랜잭션을 DB에 반영, 실제로 INSERT SQL 커밋 수행

6️⃣ 엔티티 매니저와 엔티티 매니저 팩토리 자원을 close() 호출로 반환

 


쓰기지연 예제

Team teamA = new Team();

teamA.setName("TeamA");

em.persist(teamA);

 

Team teamB = new Team();

teamB.setName("TeamB");

em.persist(teamB);

 

Member member_A = new Member();

member_A.setName("memberA");

member_A.setTeam(teamA);

 

em.persist(member_A);

 

// em.flush(); //여기서 flush()를 하느냐 마느냐로 결과가 나뉨

 

Member findMember = em.find(Member.class, member_A.getId());

Team findTeam= findMember.getTeam();

 

System.out.println(findTeam.getName());

 

flush 한 경우(쓰기지연X) 일어나는 일

create member

create team //member 와 team 이 생성된 후

 

insert team // flush로 인해 쓰기지연이 발생하지 않음 - 바로 DB로 저장됨

insert member // flush로 인해 쓰기지연이 발생하지 않음 - 바로 DB로 저장됨

print "TeamA" (memberA.getTeam()) // DB에서 TeamA를 조회해 옴

*create 는 설정한 팀과 멤버가 생성됨을 의미하는 것일 뿐 실제 작성하거나 한 건 아님

 

flush 하지 않은 경우(쓰기지연O) 일어나는 일

create member

create team //member 와 team 이 생성된 후

 

print "TeamA"

(

memberA

.

getTeam

()) //

쓰기 지연이 발생하더라도

" 영속성 컨텍스트에서

TeamA를 조회 "해옴

 

- 이후 flush() 를 따로 진행

insert team // 쓰기 지연이 발생한 부분

insert member // 쓰기 지연이 발생한 부분

*print : 영속성 컨텍스트에서 관리되는 엔티티의 데이터를 불러오는 기능


JpaRepository

 

사용방법

JpaRepository<Entity,ID> 인터페이스를 인터페이스에 extends 붙인다.

 

적용되는 내용
1. @NotRepositoryBean 가 달린 상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍된다.

더보기

@NotRepositoryBean

해당 인터페이스의 빈생성 막음(빈으로 아직 등록하지 않는다는 의미)

→ 이후 상속받으면 빈이 생성되어 사용가능해짐

즉, JpaRepository 는 데이터 액세스를 위한 핵심 기능의 종합적인 기능을 제공한다

*원래 인터페이스의 Bean 을 생성되지는 않지만 만약을 위해 명시해 두는 것

그리고 JpaRepository 는 @NotRepositoryBean 을 가진 인터페이스들을 상속받고 있기 때문에

각 인터페이스가 가진 특정 데이터 액세스 방법이나 전문적인 기술등을 사용할 수 있다

2. SpringDataJpa 에 의해 엔티티의 CRUD, 페이징, 정렬 기능 메소드들을 가진 빈이 등록된다. (상위 인터페이스들의 기능)

 

비교

- 기존 Repository 사용할 때

  // UserRepository.java

   @Repository

   public class UserRepository {

 

   @PersistenceContext

   EntityManager entityManager;

 

   public User insertUser(User user) {

         entityManager.persist(user);

         return user;

         }

 

   public User selectUser(Long id) {

        return entityManager.find(User.class, id);

         }

   }

 

- JpaRepository 적용

    // UserRepository.java

    public interface UserRepository extends JpaRepository<User, Long> {

    // 기본 메서드는 자동으로 만들어짐

    }

 


Raw JPA 테이블 매핑 기능

 

@Entity

JPQL, QuerDsl 에서 사용됨

@Table

Entity 이름과 다르게 Table 명을 지정하고 싶을때

JDBC, SQL Mapper 에서 사용됨

 

@Id

엔티티의 주키를 맵핑할 때 사용 ( *복합키를 만드는 맵핑하는 방법도 있음)

자바의 모든 primitive 타입과 그 랩퍼 타입을 사용할 수 있음( Date랑 BigDecimal, BigInteger도 사용 가능)

@GeneratedValue

주키의 생성 방법을 맵핑

생성 전략과 생성기를 설정( 기본 전략인 AUTO 와 TABLE, SEQUENCE, IDENTITY 가 있음)

@Column

unique, nullable, length, columnDefinitioin, ...등

@Temporal

JPA 2.1기준 Date 와 Calendar 만 지원

@Transient

컬럼으로 맵핑하고 싶지 않은 멤버 변수에 사용

 


Raw JPA 필드 타입 매핑 기능

 

 

1. Value 타입

@Column

String, Date, Boolean, 과 같은 타입들에 공통으로 사이즈 제한, 필드명 지정과 같이 옵션을 설정할 때 사용

* 클래스에 @Entity 가 붙으면 필드들에는 자동으로 @Column 이 붙기 때문

@Enumerated

Enum 매핑용으로 @Enumerated(EnumType.STRING) 으로 사용 권장

*Default 타입은 ORIGINAL (0, 1, 2 , ... 으로 들어가기 때문에 추후 순서가 바뀔 가능성 있음)

 

 

2. Composite Value 타입

@Embeddable

복합값 객체로 사용할 클래스 지정

@Embedded

필드에 복합 값 객체를 지정할 때

@AttributeOverrides

복합 값 객체 여러 개 지정

@AttributeOverride

복합 값 객체 필드명 선언

@Embeddable

public class Address {

private String street;

private String city;

private String state;

private String zipcode;

 

// Getters and setters

}

 

@Entity

public class Customer {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

 

private String name;

 

@Embedded

private Address address;

 

// Getters and setters

}

 

3. Collection Value 타입

* 컬럼의 값 크기제한이 있기 때문에 현업에서는 Collection Value 보다는 일대다 연관관계를 통한 Collection 타입의 변수를 주로 사용

 

- 기본 타입의 콜렉션

일반적인 Java 컬렉션 클래스(예: List, Set, Map)를 사용하여 구현 가능

컬렉션을 엔티티의 필드로 선언하고, @ElementCollection어노테이션을 사용하여 매핑

 

- Composite 타입의 콜렉션

@ElementCollection 어노테이션을 사용하여 콜렉션 매핑 가능

 


Raw JPA 연관관계 매핑 기능

 

@OneToOne

1:1 관계로 구성 한다는 것은

결국 하나의 목적에 부합되는 공통된 데이타를 관리한다고 볼 수 있으며

이것은 하나의 테이블에서 관리 할 수 있는 데이타일 가능성이 높다는 의미이므로 잘 생각해 볼 것

 

@OneToMany

속도를 위해 기본적으로 FetchType 설정이 LAZY 로 설정되어 있음

  • mappedBy : 연관관계의 주인 필드를 선택한다.
  • fetch : 글로벌 페치 전략 설정
  • cascade : 영속성 전이 기능을 사용한다.
  • targetEntity : 연관된 엔티티의 타입 정보를 설정한다.

일대다 단방향 (*일대다 양방향 관계는 없음)

//Parent 클래스

 

@OneToMany

@JoinColumn(name = "parent_id")

private List<Child> childList;

 

//Child 클래스

 

@Column(name = "parent_id")

private Long parentId;

 

일대다 단방향으로 사용될 때 발생할 수 있는 문제

 

성능문제

  • 추가적 조인 : 컬렉션이 있는 엔티티를 조회할 때마다 추가적 조인 발생 
  • N+1 문제 : DB에 불필요한 부하 발생
    엔티티를 조회할 때 연관된 엔티티들을 추가로 조회하기 위해 N개의 쿼리가 실행될 수 있음

데이터 무결성 문제

  • 고아 객체: 부모 엔티티 삭제/변경 시, 자식 엔티티가 제대로 관리되지 않을 가능성
  • 삭제 전파 : 부모 엔티티 삭제시, 연관된 자식 엔티티를 함께 삭제하도록 하는 것이 쉽지 않음

매핑 복잡성

설계의 복잡성: @OneToMany 는 직관적이지 않으며, 양방향 매핑에 비해 더 복잡하게 느껴질 수 있으므로 유지보수와 코드의 이해에 어려움을 줄 수 있음

 

 

@ManyToOne

  • optional (default true) : false로 설정하면 연관된 엔티티가 반드시 있어야 함.
  • fetch : 글로벌 패치 전략 설정 (기본이 EGEAR 로 설정되어있으나, 실무에서는 기본 LAZY로 설정 추천!)
  • cascade : 영속성 전이 기능 사용
  • targetEntity : 연관된 엔티티의 타입 정보 설정 (targetEntity = Member.class 식으로 사용)

// 다대일 양방향 관계

@Entity(name = "parent")

public class Parent {

 

@OneToMany(mappedBy="parent")

private List<Child> childList;

}

 

@Entity(name = "child")

public class Child {

 

@ManyToOne

@JoinColumn(name = "parent_id")

private Parent parent;

}

 

@JoinColumn

  • 외래 키 매핑 시 사용 (Join 을 요청하기 위한 매핑정보로 쓰인다.)
  • @ManyToOne 어노테이션과 주로 함께 쓰인다. (조인대상 컬럼 지정기능을 안쓸거면 생략해도 됨)
  • name 속성은 매핑할 외래키의 이름
  • 어노테이션을 생략해도 외래 키가 생성됨 (생략 시 외래키의 이름이 기본 전략을 활용하여 생성된다)
  • name : 매핑할 외래 키의 이름
  • referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명
  • foreignKey : 외래 키 제약조건 지정 (테이블 생성 시에만 적용됨)
  • unique/nullable/insertable/updateable/columnDefinition/table : @Column의 속성과 같음

 

@ManyToMany

  • 다대다 관계를 나타내는 매핑 정보 (N:M)
  • 다대다 설정을 하게되면 중간 매핑테이블(JoinTable)이 자동으로 생성된다.
  • 중간 매핑 테이블은 JPA상에서 숨겨져서(Entity 정의 없이) 관리된다.
  • 매핑 테이블 관리가 불가능하여서 실무에서는 잘 사용하지 않는 기능

@Entity

public class Parent {

 

      @ManyToMany(mappedBy = "parents")

       private List<Child> childs;

}

 

@Entity

public class Child {

 

       @ManyToMany

       @JoinTable(

       name = "parent_child",

       joinColumns = @JoinColumn(name = "parent_id"),

       inverseJoinColumns = @JoinColumn(name = "child_id")

       )

       private List<Parent> parents;

}

 

@ManyToMany 보다

TableA(@OneToMany) >> MappingTable(@ManyToOne, @ManyToOne) >> TableB(@OneToMany) 사용

@Entity

public class Parent {

 

       @OneToMany(mappedBy = "parent")

       private List<ParentChild> parentChilds;

}

 

@Entity

public class ParentChild {

 

       @ManyToOne

       @JoinColumn("parent_id")

       private Parent parent;

      

       @ManyToOne

       @JoinColumn("child_id")

       private Child child;

}

 

@Entity

public class Child {

 

       @OneToMany(mappedBy = "child")

       private List<ParentChild> parentChilds;

}

 

 

https://teamsparta.notion.site/JPA-0085a4d21ae84dc48e8eda40b621e5a0#6a4ee915e59248c79744d051e35e2094

 

JPA 연관관계 매핑 에러 잡기 | Notion

1. JPA 연관관계 원리

teamsparta.notion.site

 

 

 

 

 

 

 

 

 

'Organizing Docs > Java Docs' 카테고리의 다른 글

SubQuery, JPAExpressions  (0) 2024.07.01
JPQL - 복잡한 쿼리를 수동으로 작성하고 실행하는 방법  (0) 2024.06.30
Spring Data JPA  (0) 2024.06.28
페이징  (0) 2024.06.28
MyBatis  (0) 2024.06.27