보글보글 개발일지
article thumbnail
반응형

섹션 0. 강좌 소개

  • 실무는 수십 개 이상의 복잡한 객체와 테이블 사용
  • 객체와 테이블 설계하고 매핑하는 방법
  • JPA 내부 동작 방식 이해

섹션 1. JPA 소개

SQL 중심적인 개발의 문제점

  • 무한 반복, 지루한 코드
  • 객체 CURD - 필드 추가: 쿼리를 다 수정해야한다. 즉, SQL에 의존적인 개발을 피하기 어렵다.
  • 패러다임의 불일치 : 객체 vs 관계형 데이터 베이스
  • 객체를 관계형 DB에 저장 --> 개발자가 SQL 매퍼가 된다..
  • 객체와 관계형 데이터 베이스의 차이
    • 1. 상속(관계형 DB에는 없다.)
    • 2. 연관관계: 객체는 한방향으로만 갈 수 있고, 테이블은 양방향 조회 가능 
    • 3. 데이터 타입
    • 4. 데이터 식별 방법
  • 무한 반복, 지루한 코드
  • 객체 CURD - 필드 추가: 쿼리를 다 수정해야한다. 즉, SQL에 의존적인 개발을 피하기 어렵다.
  • 엔티티 신뢰 문제
  • 모든 객체를 미리 로딩할 수는 없다.
  • SQL을 직접 다루면 진정한 의미의 계층 분할이 어렵다.
  • 객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수는 없을까? --> JPA!

JPA 소개

- JPA란 Java Persistence API의 약어. 자바 진영의 ORM 기술 표준
- ORM: Object relational mapping(객체 관계 매핑), 객체는 객체대로 설계, 관계형 데베는 관계형 데베대로 설계. ORM 프레임 워크가 중간에서 매핑
- JPA는 애플리케이션과 JDBC 사이에서 동작, 인터페이스의 모음
- 생산성 - JPA와 CRUD

  • 저장: jpa.persist(member)
  • 조회: Member member = jpa.find(memberId)
  • 수정: member.setName("변경할 이름")
  • 삭제: jpa.remove(member)

- 유지 보수: 필드만 추가하면 됨, SQL은 JPA가 처리
- JPA와 패러다임의 불일치 해결
- JPA의 성능 최적화 기능

  1. 1차 캐시와 동일성(identity) 보장-> 같은 트랜잭션 안에서는 같은 엔티티를 반환(캐싱)
  2. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)->트랜잭션을 커밋할 때까지 INSERT SQL을 모았다가 JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송
  3. 지연 로딩(Lazy Loading)-> 지연 로딩: 객체가 실제 사용될 때 로딩, 즉시 로딩: JOIN SQL로 한번에 연관된 객체까지 미리 조

- ORM은 객체와 RDB 두 기둥위에 있는 기술

섹션 2. JPA 시작하기

Hello JPA - 프로젝트 생성

  • H2, maven
  • 프로젝트 세팅은 PPT에 코드가 있고, 자바 11로 세팅하고 싶어 아래 코드를 pom.xml에 추가하였다.
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.0</version>
</dependency>

- 데이터 베이스 방언: JPA는 특정 데이터베이스에 종속 X, 각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 다름

Hello JPA - 애플리케이션 개발

- EntityManagerFactory는 애플리케이션 로딩 시점에 딱 하나만 만들어야한다.
- EntityManager은 트랜잭션 단위로 만들어야한다. 쓰레드 간에 공유X.
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행

//Member.java

@Entity
public class Member {
    @Id
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

- @Table(name = "USER")
- JPA가 매핑정보를 보고 넣어줌. 관례를 따른다. 위에처럼 명시하면 USER라는 테이블에 저장
- 어노테이션으로 필요한 매핑해준다.

//JpaMain.java
public static void main(String[] args){
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();
    tx.begin();

        try {
            Member member = new Member();
            member.setId(1L);
            member.setName("HelloA");
            em.persist(member);
            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally {
            em.close();
        }
    emf.close();

}

- 회원 등록의 경우이다.

    public static void main(String[] args){
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {
            Member findMember = em.find(Member.class, 1L);
            em.remove(findMember);
            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();

    }

- 회원 삭제

try {
    Member findMember = em.find(Member.class, 1L);
    findMember.setName("HelloJPA");
    tx.commit();
} catch (Exception e){
    tx.rollback();
} finally {
    em.close();
}

- 회원 수정 : set만 하면 JPA가 관리를 하므로 commit시점에 변경 감지를 한다. 바뀌었으면 업데이트 쿼리 날리고 커밋.

    List<Member> result = em.createQuery("select m from Member as m", Member.class)
            .getResultList();//멤버 객체를 대상으로 쿼리

- JPQL: 필요 데이터만 불러오기 위해, 엔티티 객체를 대상으로 쿼리 진행하기 위해 필요

섹션 3. 영속성 관리 - 내부 동작 방식

영속성 컨텍스트 1

- JPA에서 가장 중요한 2가지
   객체와 관계형 데이터베이스 매핑하기, 영속성 컨텍스트

[엔티티 매니저 팩토리와 엔티티 매니저]
- 엔티티 매니저 팩토리를 통해서 고객이 요청이 올 때마다 엔티티 매니저를 생성하고, 이는 내부적으로 데이터 베이스 커넥션을 사용해서 DB를 사용한다. 
- 영속성 컨텍스트: "엔티티를 영구 저장하는 환경"이라는 뜻. EntityManager.persist(entity)
- 영속성 컨텍스트는 논리적인 개념이라 눈에 보이지 않고, 엔티티 매니저를 통해 영속성 컨텍스트에 접근한다.
- 엔티티 매니저를 생성하면 그 안에 1대1로 영속성 컨텍스트가 생긴다. 눈에 보이지 않는 공간이 생긴다고 이해하면 쉽다.

[엔티티의 생명주기]
- 비영속(new/transiet): 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");


- 영속 (managed): 영속성 컨텍스트에 관리되는 상태

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);


- 준영속 (detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed): 삭제된 상태

//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
//객체를 삭제한 상태(삭제)
em.remove(member);

[영속성 컨텍스트의 이점]
- 1차 캐시
- 동일성(identity) 보장 
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind) 
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)

영속성 컨텍스트 2

[엔티티 조회, 1차 캐시]

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId(101L);
member.setName("회원12");

//영속
System.out.println("==BEFORE==");
em.persist(member);
System.out.println("==AFTER==");

Member findMember = em.find(Member.class, 101L);
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getName() = " + findMember.getName());


tx.commit();

결과

- 조회를 했는데 select 쿼리 안나옴-> em.persist 할 때 1차 캐시에 저장 -> 똑같은 PK로 값 조회했으므로 디비가 아닌 1차 캐시에 있는 걸로 조회!

    Member findMember1 = em.find(Member.class, 101L);
    Member findMember2 = em.find(Member.class, 101L);

결과

- select 쿼리 한번만 나옴. 처음 101번 가져올 때 영속성 컨텐츠에 올림. 두번째는 1차 캐시에서 조회.

[영속 엔티티의 동일성 보장]

Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println("result = " + (findMember1 == findMember2));
tx.commit();

- 결과는 result = true가 나온다. ->1차 캐시 있어서 가능. 같은 트랜잭션 안에서 실행해야 한다.

[엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연]

- 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.

tx.begin(); // [트랜잭션] 시작
em.persist(memberA); //INSERT SQL 생성, 1차 캐시에 저장
em.persist(memberB); //여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
tx.commit(); // [트랜잭션] 커밋

- 버퍼링 과정. 쌓아두고 데이터베이스에 한번에 쿼리 두개를 보냄. 배치 사이즈만큼 모은다.
- 이런걸 잘 활용해서 성능에서 이점을 얻을 수 있다.

[엔티티 수정 - 변경 감지]

Member member1 = em.find(Member.class, 159L);
Member member2 = em.find(Member.class, 160L);

em.persist(member1);
em.persist(member2);
System.out.println("=======");

tx.commit();

- 위와 같이 멤버 저장한 뒤에 수정을 하려할 때,

Member member = em.find(Member.class, 150L);
member.setName("2222");

- persist를 하면 안된다. 위 두줄로만 값 변경이 가능하다. by 변경 감지. commit 시점에 변경 감지!

플러쉬

-플러쉬: 영속성 컨텍스트의 변경 내용을 데이터 베이스에 반영하는 작업

[플러시 발생]
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터 베이스에 전송 (등록, 수정, 삭제 쿼리)

[영속성 컨텍스트를 플러시 하는 방법]
- em.flush() - 직접 호출 (사용할일 거의 없음-미리 쿼리 보고싶거나 미리 데베에 반영하고 싶은 경우 강제호출)

Member member = new Member(200L, "member200");
em.persist(member);

em.flush();

          - 플러시한다고 1차캐시가 지워지는게 아니라 바뀐게 데이터베이스에 반영되는 과정
- 트랜잭션 커밋 - 플러시 자동 호출 
- JPQL 쿼리 실행 - 플러시 자동 호출

[플러시 모드 옵션] - 우리가 쓸일은 거의 없다. 가급적 손안대고 AUTO로 쓰기
em.setFlushMode(FlushModeType.COMMIT)
• FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시 (기본값)
• FlushModeType.COMMIT: 커밋할 때만 플러시

[플러시는!]
- 영속성 컨텍스트를 비우지 않음
- 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
- 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면 됨

준영속 상태

- 영속 -> 준영속
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
- 영속성 컨텍스트가 제공하는 기능을 사용 못함


[준영속 상태로 만드는 방법]

//영속 상태 
Member member = new Member(200L, "member200");
member.setName("AAAAA");

em.detach(member); //JPA에서 관리 안함. 트랜잭션 커밋할 때 아무일도 일어나지 않음

- em.detach(entity): 특정 엔티티만 준영속 상태로 전환

//영속 상태
Member member = new Member(200L, "member200");
member.setName("AAAAA");

em.clear();//엔티티 매니저안에 있는 영속성 컨텍스트 통째로 지움


- em.clear(): 영속성 컨텍스트를 완전히 초기화

//영속 상태
Member member = new Member(200L, "member200");
member.setName("AAAAA");

em.close();


- em.close(): 영속성 컨텍스트를 종료

섹션 4. 엔티티 매핑

객체와 테이블 매핑

[엔티티 매핑 소개]
• 객체와 테이블 매핑: @Entity, @Table
• 필드와 컬럼 매핑: @Column
• 기본 키 매핑: @Id
• 연관관계 매핑: @ManyToOne,@JoinColumn

[@Entity]

  • @Entity가 붙은 클래스는 JPA가 관리한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 필수로 @Entity를 쓴다.
    - 주의
        • 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
        • final 클래스, enum, interface, inner 클래스 사용X
        • 저장할 필드에 final 사용 X
    - 속성: name
        • JPA에서 사용할 엔티티 이름을 지정한다.
        • 기본값: 클래스 이름을 그대로 사용(예: Member)
        • 같은 클래스 이름이 없으면 가급적 기본값을 사용한다.

[@Table]

  • 엔티티와 매핑할 테이블을 지정

데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 자동 생성(DB 테이블 생성)
  • 테이블 중심->객체 중심
  • 데베 방언을 활용해서 데베에 맞는 적절한 DDL 생성 --> 개발에서만 써야한다. 운영에서 쓰면 안됨.

[옵션 설명]

  • create: 기존테이블 삭제 후 다시 생성 (DROP + CREATE)
  • create-drop: create와 같으나 종료시점에 테이블 DROP
  • update: 변경분만 반영(운영DB에는 사용하면 안됨)
  • validate: 엔티티와 테이블이 정상 매핑되었는지만 확인
  • none: 사용하지 않음
<property name="hibernate.hbm2ddl.auto" value="create" />
  • persistence.xml에서 수정해준다.

[주의]

  • 개발 초기: create, update
  • 테스트 서버(여러명의 개발자가 함께 쓰는 중간서버): update, validate - create쓰면 데이터 다 날라간다.
  • 스테이징, 운영서버는 validate, none 
  • 운영 장비에는 절!대! create, create-drop, update 쓰면 안된다. 그냥 none 쓰는게 좋다.

 

  • 애플리케이션 로딩 시점에 시스템이 자동으로 alter를 쓰는게 굉장히 위험하다.
  • 가급적 alter 테이블은 스크립트를 직접 만든걸 테스트 서버에 반영을 해봐야한다. 
  • 직접 스크립트를 짜서 적용하는 걸 권장한다! 여러명이 쓰는 개발서버나 스테이징, 운영서버는 가급적 쓰지 않는걸 권장한다.
  • 스크립트 만들어 주니까, 운영서버에 반영할 때 만들어주는 걸 다듬어서 넘기는걸 선호하신다.
  • 근본적으로 웹 애플리케이션 계정은 alter, drop 못하도록 계정 분리하는게 맞다.

[DDL 생성 기능]

  • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.
    • 제약 조건 추가 @Column(nullable = false, length = 10)

필드와 컬럼 매핑

[매핑 어노테이션 정리]

@Column 컬럼 매핑

@Column(name = "name")//객체는 username쓰고싶고, DB에는 name이라 쓰고싶은 경우
private String username;

  • insertable, updatable: 등록, 변경 가능 여부 (기본: TRUE)
  • nullable(DDL): null 값의 허용 여부를 설정한다. false로 설정하면 DDL 생성 시에 not null 제약조건이 붙는다.
  • unique(DDL): 잘 안씀. @Table의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제 약조건을 걸 때 사용한다.
  • columnDefinition (DDL): 데이터베이스 컬럼 정보를 직접 줄 수 있다. ex) varchar(100) default ‘EMPTY'
  • length(DDL): 문자 길이 제약조건, String 타입에만 사용한다. (기본: 255)
  • precision, scale(DDL) BigDecimal 타입에서 사용한다(BigInteger도 사용할 수 있다). precision은 소수점을 포함한 전체 자 릿수를, scale은 소수의 자릿수 다. 참고로 double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정 밀한 소수를 다루어야 할 때만 사용한다. (기본: precision=19, scale=2)

@Temporal 날짜 타입 매핑

@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;

@Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;

• TemporalType.DATE: 날짜, 데이터베이스 date 타입과 매핑 (예: 2013–10–11)
• TemporalType.TIME: 시간, 데이터베이스 time 타입과 매핑 (예: 11:11:11)
• TemporalType.TIMESTAMP: 날짜와 시간, 데이터베이 스 timestamp 타입과 매핑(예: 2013–10–11 11:11:11)

@Enumerated enum 타입 매핑

@Enumerated(EnumType.STRING)
private RoleType roleType;

자바 enum 타입을 매핑할 때 사용. ORDINAL 사용X

• EnumType.ORDINAL: enum 순서를 데이터베이스에 저장 --> 매우 위험. 중간에 타입 추가되면 문제 생긴다.
• EnumType.STRING: enum 이름을 데이터베이스에 저장 --> 문자 그대로 들어감. 이걸로 써야한다.

@Lob: BLOB(나머지), CLOB(문자) 매핑. 속성 없음

@Lob //데베에 큰 콘텐츠 넣고 싶을 때
private String description;

@Transient 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

@Transient //DB에는 관련 없이 메모리에서만 계산하고 싶을 때
private int temp;

기본 키 매핑

[기본 키 매핑 어노테이션]
@Id, @GeneratedValue

[기본 키 매핑 방법]

  • 직접 할당 : @Id만 사용
  • 자동 생성: @GeneratedValue - DB가 자동으로 값을 만들어준다. 기본값: AUTO(DB방언에 맞춰 자동으로)
    • IDENTY 전략: 기본 키 생성을 데베에 위임. "난 모르겠고 DB야 알아서 해줘!"
      ex) MySQL의 auto_increment
    • SEQUENCE 전략: 데베 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데베 오브젝트(예: 오라클 시퀀스)
      테이블마다 따로 시퀀스 만들고 싶으면 @SequenceGenerator 쓰면 된다.
      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ")와 같이 사용한다.
    • TABLE 전략: 키 생성 전용 테이블을 하나 만들어서 데베 시퀀스를 흉내. 모든 디비에 다 적용 가능하나 성능이 구리다.
      @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ")
      운영에서 쓰기에는 부담스럽다. 잘 쓰지 않는다.
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
        initialValue = 1, allocationSize = 1)

@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ", allocationSize = 1)


[권장하는 식별자 전략]

  • 기본 키 제약 조건: 널 아니고, 유일, 변하면 안된다. 
  • 미래까지 변하지 않는 자연키(비지니스적으로 의미 있는 주민번호 같은것.) 찾기 어려우므로 대리키(대체키) 사용해야한다.
  • 주민번호도 기본키로 적절하지 않다. -->주민번호 저장하면 안되면? 문제된다.
  • 권장: "Long형 + 대체키 + 키" 생성전략! -->AUTO_INCREMENT, SEQUENCE 오브젝트 쓰거나 회사내 룰 대로 쓰기.

[IDENTITY 전략의 특징]

  • Id에 값넣으면 안되고 DB에 insert해야하는데 DB에서 값 날라오면 그때 세팅을 한다.
  • Id값 알 수 있는건 DB에 들어간 후!
  • 영속 상태가 된건 @Id에 값이 있는 건데 identity는 DB에 값 들어가기 전까지 Id값을 모른다.
  • IDENTITY 전략에서만 em.persist(member) 호출하는 시점에 바로 데이터베이스 insert 쿼리를 날린다.
  • 모아서 Insert 하는게 불가능 하다는 단점이 있지만.. 크게 중요하지 않다.

[SEQUENCE 전략의 특징]

@Entity
@SequenceGenerator(
    name = “MEMBER_SEQ_GENERATOR",
    sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 1)
public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE,
	generator = "MEMBER_SEQ_GENERATOR")
	private Long id;
}
  •  1부터 시작, 1씩 증가: start with 1 increment by 1
  • call next value for MEMBER_SEQ 라는 쿼리가 나간다. 이는 데이터 베이스에 MEMBER_SEQ값 내놔라! 라는 뜻이다.
  • 디비에서 값 얻어와서 멤버 아이디에 값 넣어주는 과정 필요하다. 그 다음에 영속성 컨텍스트 저장을 한다.
  • 버퍼링 가능. 커밋 시점에 insert 쿼리 나가기 때문! 
  • 하지만, 계속 디비에 접근하므로 성능상 좋지 않다고 생각할 수 있다.
    • 따라서 allocationSize 설정을 할 수 있다.
    • 미리 50개를 땡겨온다. 50개를 미리 올려두고 쓴다.   

실전 예제 1 - 요구사항 분석과 기본 매핑

데이터 중심 설계의 문제점
• 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식
• 테이블의 외래키를 객체에 그대로 가져옴
• 객체 그래프 탐색이 불가능
• 참조가 없으므로 UML도 잘못됨

섹션5. 연관관계 매핑 기초

단방향 연관관계

객체가 지향하는 패러다임과 관계형 DB가 지향하는 패러다임이 다르기 때문에 둘 간 차이에서 오는 어려움이 있다.

  1. 목표
    • 객체와 테이블 연관관계의 차이를 이해
    • 객체의 참조와 테이블의 외래 키를 매핑
  2. 용어 이해
    1. 방향(Direction): 단방향, 양방향
    2. 다중성(Multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1),
    3. 다대다(N:M) 이해
    4. 연관관계의 주인(Owner): 객체 양방향 연관관계는 관리 주인이 필요

[연관관계가 필요한 이유]

'객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.'

  • 예제 시나리오 
    • 회원과 팀이 있다.
    • 회원은 하나의 팀에만 소속될 수 있다.
    • 회원과 팀은 다대일 관계다.
  •  

//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId());
em.persist(member);

저장을 member.setTeamId로 하는게 객체지향스럽지 않다.

위와 같이 join을 해서 데이터를 확인할 수 있다.

조회할 때 매우 불편하다. 

Member findMember = em.find(Member.class, member.getId());
Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class,findTeamId);
  • 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계를 만들 수 없다.
    • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
    • 객체는 참조를 사용해서 연관된 객체를 찾는다.
    • 테이블과 객체 사이에는 이런 큰 간격이 있다.
  • 객체 지향 모델링 - 단방향 연관관계
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

 

//팀 저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);
//회원 저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); //단방향 연관관계 설정, 참조 저장
em.persist(member);

//조회
Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
Team findTeam = findMember.getTeam();
// 새로운 팀B
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
// 회원1에 새로운 팀B 설정
member.setTeam(teamB);


양방향 연관관계와 연관관계의 주인 1- 기본

@OneToMany(mappedBy = "team")//일대다 매핑에서 뭐와 연결되어 있는지 써준다.
        //나는 team으로 매핑이 되어있다!
List<Member> members = new ArrayList<Member>();
  • mappedBy는 객체와 테이블이 관계를 맺는 차이를 이해해야한다.
    • 객체 연관관계 = 2개
      • 팀 -> 회원 연관관계 1개(단방향)
      • 회원 <-> 팀의 연관관계 1개(양방향)
    • 테이블 연관관계 = 1개
      • 회원 -> 팀 연관관계 1개(단방향)
    • 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 2개다.
    • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.

  • 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
  • MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽으로 조인할 수 있다.)
  • 연관관계의 주인(Owner)
    • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
    • 주인이 아닌쪽은 읽기만 가능
    • 주인이 아니면 mappedBy 속성으로 주인 지정
    • 주인은 mappedBy 속성 사용X
    • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인: 외래 키가 있는곳!


양방향 연관관계와 연관관계의 주인 2- 주의점, 정리

  • 실수
    • 연관관계의 주인에 값을 입력하지 않음

  • 양방향 매핑시 연관관계의 주인에 값을 입력해야 한다. (순수한 객체 관계를 고려하면 항상 양쪽다 값을 입력해야 한다.)
  • team.getMembers().add(member)를 해준다. --> 더 업그레이드 해서 연관관계 편의 메소드를 생성하자.

  • 메소드 이름은 set 이런거 보다 changeTeam 써서 명시해주는 게 좋음!
  • 양방향 연관관계 주의 - 실습 
    • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자 
    • 연관관계 편의 메소드를 생성하자
    • 양방향 매핑시에 무한 루프를 조심하자 • 예: toString(), lombok, JSON 생성 라이브러리
  • 양방향 매핑 정리
    • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
    • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
    • JPQL에서 역방향으로 탐색할 일이 많음
    • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않음)

실전 예제 2 - 연관관계 매핑 시작

- Member.java에 mappedBy를 해준다.

@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();

- Order.java에는 이렇게!

@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;

- 최대한 단방향으로 해라! 조회할 때 양방향으로 만들어도 된다.

반응형
profile

보글보글 개발일지

@보글

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!