엔티티매핑의 기초

기본키 매핑 전략

영속성 컨텍스트는 엔티티를 식별자 값으로 구분하므로, 엔티티를 영속 상태로 만들려면 식별자 값이 반드시 필요하다. 아래에서 간단히 기본키 매핑 전략에 대해서 알아보자.

  • 직접할당 : em.persist() 를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야한다.
      Board board = new Board();
      board.setId("id1");
      em.persist();
    
  • IDENTITY : 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
      @Id
      @GeneratedValue(strategy=GenerationType.IDENTITY)
    
  • SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
      @Entity
      @SequenceGenerator(
      	name = "BOARD_SEQ_GENERATOR",
      	sequenceName = "BOARD_SEQ"
      	initialValue = 1, 
      	allocationSize = 1)
      ...
      	@Id
      	@GeneratedValue(stratagy=GenerationType.SEQUENCE, generator="BOARD_SEQ_GANERATOR")
    
  • TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

객체와 테이블 매핑 방법

@Entity : 이 클래스가 테이블과 매핑하는 클래스라는 것을 JPA에게 알려준다

@Table :

  • name 속성을 사용해서 테이블과 매핑한다.
  • uniqueConstraints 사용하여 unique key 를 설정할 수 있다.

@Id :

  • 이 어노테이션이 붙은 (엔티티 클래스의) 필드를 테이블의 기본키 (Primary key)에 매핑한다.
  • @Id가 사용된 필드를 식별자 필드라 한다
  • @GeneratedValue 어노테이션으로 자동 키생성

@Column :

  • 필드를 테이블의 컬럼을 매핑한다.
  • name 속성을 사용해서 Member 엔티티의 username 필드를 MEMBER 테이블의 NAME 컬럼에 매핑했다
  • nullable 속성 기본값, true
  • @Column 을 생략시 기본값으로 설정되게 되는데, java 의 경우 예외가 발생한다.
    1. int 에 null 을 넣을수 없으므로 nullable 값이 false 로 설정 된다.
    2. Integer 의 경우 null 이 가능하므로 nullable 값이 true 로 설정 된다.
    3. int 로 선언한 필드에 @Column 을 선언한 경우, nullable 설정을 꼭 해야한다.

@Transient : 해당 필드는 매핑하지 않는다. 임시로 값을 보관할 때 사용한다.

@Enumerated : enum 타입 매핑시 사용

@Temporal : 날짜 타입 매핑시 사용, 생략시 Date 와 가장 유사한 timestamp 로 정의한다..

  1. datetime : MySQL
  2. timestamp : H2, Oracle, PostgreSQL.

연관관계

객체연관관계와 테이블연관관계 비교

    //단방향 연관관계
    class A {
        B b;
    }
    class B {}
    //양방향 연관관계
    class A {
        B b;
    }
    class B {
        A a;
    }
  • 객체는 참조로 연관관계를 맺는다.
  • 테이블은 외래키로 연관관계를 맺는다.
  • 참조를 사용하는 객체의 연관관계는 단방향이다.
    1. A → B (a.b)
  • 외래키를 사용하는 테이블의 연관관계는 양방향이다.
    1. A JOIN B 가 가능하면, B JOIN A 도 가능
  • 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
    1. A → B (a.b)
    2. B → A (b.a)

ref.(책)자바ORM표준 JPA 프로그래밍

다대일 연관관계 (단방향)

//매핑한 회원 엔티티
@Entity
public class Member {
	@Id
	@Column(name = "MEMBER_ID" )
	private String id;

	private String username;

	//연관관계매핑
	@ManyToOne
	@JoinColumn(name= "TEAM_ID" )
	private Team team;

	//연관관계설정
	public void setTeam(Team team) {
		this.team = team;
	}
	...
	//getter, setter
}
//매핑한 팀 엔티티
@Entity
public class Team {
	@Id
	@Column(name = "TEAM_ID")
	private String id;

	private String name;
	...	
	//Getter, Setter
}

@ManyToOne :

  • N:1 관계 매핑 정보에 사용
  • optional : 기본 true, false 로 설정시 연관된 엔티티가 항상 있어야함
  • fetch : 글로벌패치 전략 (“FetchType.EAGER”, “FetchType.LAZY”)

@JoinColumn(name= “TEAM_ID” ) :

  • 외래키 매핑시 사용 한다.
  • name 속성에 매핑할 외래키 이름을 사용(생략가능)
  • 생략시 기본전략을 사용, 기본전략은 “필드명” + “_” + “컬럼명”

@ManyToOne 의 optional 속성이 true 이거나 @JoinColumn 의 nullable 속성이 true 인 경우는 Team 이 없는 Member 의 조회도 보장해야 하므로 Outer Join을 수행하게 된다

양방향 연관관계 매핑

//매핑한 팀 엔티티 (양방향 관계설정)
@Entity
public class Team {
	@Id
	@Column(name = "TEAM_ID")
	private String id;

	private String name;

	//==추가==//
	@OneToMany(mappedBy = "team")
	private List<Member> merbers = new ArrayList<Member>();
	...	
	//Getter, Setter
}

팀과 회원의 일대다 관계설정을 위해서 팀 엔티티에 컬렉션인 List member 를 추가했다.

@OneToMany : 추가된 정보와 일대다 관계 매핑을 위해 사용했다.

  • mappedBy : 양방향 매핑시 사용한다.
  • 반대쪽 매핑의 필드 이름을 값으로 사용 한다.(반대쪽 매핑이 Member.team 이므로 team 사용)

연관관계의 주인

DB 테이블과 다르게 객체는 양방향 연관관계라는 것이 없으며 서로 다른 연관관계를 로직상 표현으로 양방향 관계로 보이게 하는것이다.

엔티티를 양방향으로 매핑하면 객체의 참조는 다음과 같이 둘 인데 (회원→팀, 팀→회원) 외래키는 하나이다.

둘중 어떤 관계를 사용해서 외래키를 관리해야 할까? 이런 차이로 인해 객체에서는 연관 관계의 주인을 설정해야한다.

연관관계의 주인만 DB 연관관계와 매핑되고 외래키 관리(등록, 수정, 삭제) 할 수 있다. (반대의 경우 일기만 가능)

  1. 주인은 mappedBy 속성을 사용하지 않는다.
  2. 주인이 아니면 mappedBy 속성을 사용하여 연관관계의 주인을 지정해야 한다.
  3. 연관관계의 주인은 테이블의 외래키가 있는 곳으로 설정한다. (회원테이블의 “TEAM_ID” )

ref.(책)자바ORM표준 JPA 프로그래밍

양방향 연관관계의 주의점

public void testSaveNotOwner() {
	//회원l저장
	Member memlberl = new Member("memberl", "회원l");
	em.persist(memberl);

	//회원2저장
	Member member2 = new Member("member2" , "회원2");
	em.persist(member2);

	Team teaml = new Team("teaml", "팀l");
	//주인이 아닌 곳만 연관관계 설정
	teaml.getMembers() .add(memberl);
	teaml. getMembers () . add (member2) ;

	em.persist(teaml);
}
  1. 연관 관계의 주인만 외래키의 값을 변경할 수 있으나 위 코드는 연관 관계 주인인 Member.team 에 아무 값도 입력하지 않았다. 따라서 TEAM_ID 외래의 값도 null 이 저장된다. 실제 테이블에 저장된 결과는 아래와 같다.

    ref.(책)자바ORM표준 JPA 프로그래밍

  2. JPA를 떠나 객체의 관점에서만 보면 순수한 객체 양쪽 방향 모두에 값을 입력 하는것이 가장 안전하다. 반대방향에 연관관계를 설정하지 않은 상태로 회원을 출력해보면 결과는 “0” 이 나온다.

     public void test_순수한객체_양방향() {
     	//팀l
     	Team team1 = new Team("team1", "팀1");
     	Member member1 = new Member("member1", "회원1");
     	Member member2 = new Member ("member2", "회원2");
     	member1.setTeam(team1); //연관관계 설정 member1 -> team1
     	member2.setTeam(team1); //연관관계 설정 member2 -> teaml
    
     	List<Member> members = teaml.getMembers ();
     	System.out.println("members.size = " + members. size()) ;
     }
    
     //결과: members.size = 0
    
  3. 객체까지 고려하면 양쪽 다 관계를 맺어야한다. JPA를사용해서 완성한 코드를 확인하면 아래와 같다. 양쪽 방향 모두 값을 입력하지 않으면 JPA를 사용하지 않는 순수한 객체 상태에서 심각한 문제가 발생할 수 있다

    // 순수객체 상태까지 고려된 JPA 사용하여 완성된 코드
    public void test_ORM_양방향() {
    	//팀l 저장
    	Team team1 = new Team("teaml", "팀l");
    	em.persist(tema1);
    
    	Member memberl = new Member("member1", "회원1");
    	//양방향 연관관계 설정
    	member1.setTeam(team1); //연관관계 설정 member1 > team1
    	team1.getMembers().add(member1); //연관관계 설정 team1 -> member1
    	em.persist(member1);
    
    	Member member2 = new Member("member2", "회원2") ;
    	//양방향 연관관계 설정
    	member2.setTeam(team1) ; //연관관계 설정 member2 -> team1
    	team1.getMembers().add(member2) ;//연관관계 설정 team1 -> member2
    	em.persist(member2) ;
    }
    
  4. 결론 : 객체의 연관관계는 양쪽 모두 관계를 맺어줘야 한다.

     //양방향 관계에서 두 코드는 하나인 것처럼 사용하는 것이 안전하다.
     member.setTeam (team);
     team.getMembers().add(member);
    
     //편의성 메소드를 추가하여 수정된 Member 클래스
     public class Member (
     	private Team team;
     	public void setTeam(Team team) {
     		this.team = team;
     		team.getMembers().add(this);
     	}
       ...
     }
    
     //연관관계설정 부분 수정
     member1.setTeam(team1);
     //team.getMembers().add(member1);
     member2.setTeam(team1);
     //team.getMembers().add(member2);
    
     public void test_ORM_양방향_리팩토링 () {
     	Team team1 = new Team("team1", "팀1");
     	em.persist(team1);
     	Member member1 = new Member("member1", "회원l");
     	member1.setTeam(teaml); //양방향설정
     	em.persist (member1);
     	Member member2 = new Member("member2", "회원2");
     	member2.setTeam(team1) ; //양방향설정
     	em.persist(member2);
     }
    

정리

  • 단방향 매핑만으로 테이블과 객체의 연관관계 매핑은 이미 완료되었다
  • 단방향을 양방향으로 만들면 반대방향으로 객체 그래프 탐색 기능이 추가된다.
  • 양방향 연관관계를 매핑하려면 객체에서 양쪽 방향을 모두 관리해야 한다.
ryan's profile image

ryan

2020-12-02 10:00

ryan 님이 작성하신 글 더 보기