본문 바로가기
프로그래밍/JPA

[JPA] 앤티티 매핑, 연관관계 매핑, 상속관계 매핑

by yonmoyonmo 2021. 5. 28.
김영한님의 책 [JPA 프로그래밍]을 읽고 요약한 내용입니다.

클래스 어노테이션

@Entitiy

테이블과 매핑할 클래스에 필수로 써야함

기본생성자 필수

@Table

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

name, uniqueConstraints 등의 속성이 있음

@Access

JPA가 엔티티에 접근하는 방식 지정

필드 어노테이션

@Id

Primary Key 매핑

데이터베이스 생성 키 사용 등의 설정을 할 수 있음

@Column

클래스 필드와 테이블의 콜럼을 매핑

name, nullable,length 등의 속성이 있음

@Enumerated

자바 enum 타입의 필드를 테이블 콜럼과 매핑

@Enumerated(EnumType.STRING) : enum의 이름을 저장

@Enumerated(EnumType.ORDINAL) : enum에서 순서를 저장

@Temporal

자바의 날짜타입을 테이블 콜럼과 매핑

@Temporal(TamporalType.DATE)

@Temporal(TamporalType.TIME)

@Temporal(TamporalType.TIMESTAMP)

요런 식으로 사용함

@Lob

CLOB, BLOB 타입 매핑

@Transient

필드를 테이블과 매핑하지 않는다

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

어플리케이션 실행 시점에 데이터베이스 테이블을 엔티티에 따라 자동 생성함 -> DDL

hibernate ddl 옵션

create 기존 테이블 삭제 후 새로 생성 drop + create

create-drop create + 어플리케이션 종료 시 생성된 테이블 삭제 drop + create + drop

update 테이블과 엔티티 매핑 정보를 비교해서 변경된 사항만 수정, 없으면 새로 생성

validate 데이터베이스 테이블과 엔티티 매핑 정보를 비교해서 차이가 있으면 경고를 남기고 어플리케이션 실행 중단

none 아무고토 안함

프라이머리 키 매핑

직접 키를 할당해도 되지만

MySQL의 AUTO_INCREMENT 같은 기능을 사용하여 생성된 값을 기본 키로 사용하려 하거나 오라클의 시퀀스 오브젝트 같은 것을 사용하려 할 때

@GeneratedValue 어노테이션을 함께 사용할 수 있다.

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY) 이런 식으로 사용하면 됨

연관관계 매핑

객체의 참조와 테이블의 외래 키 매핑

테이블과 객체의 차이 :

객체 참조를 통한 연관관계는 언제나 단방향이다. 양방향으로 만들고 싶으면 참조되는 객체에도 참조하는 객체의 참조를 필드에 추가하면 된다. 그러나 테이블은 한 쪽에 가지는 외래 키 하나만 있으면 양방향으로 조인이 가능하다. ---> 객체 참조 필드는 2개인데 테이블은 외래 키 하나만 있으면 되는 패러다임 불일치가 생긴다 ---> JPA 어노테이션으로 매핑하여 문제를 해결할 수 있다. --->이 때 외래 키를 가지게 되는 엔티티를 연관관계의 주인이라고 한다.

@Entity
public class Student{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="STUDENT_ID")
	private Long id;

	@ManyToOne
	@JoinColumn(name="SCHOOL_ID")
	private School school;
}

@Entity
public class School{
	@Id
	@Column(name="SCHOOL_ID")
	private Long id;
}

 

요런식으로 연관관계를 매핑하는 어노테이션을 쓸 수 있다.

@ManyToOne 같은 것은 연관관계의 다중성을 나타낼 때 사용하며 다양한 속성이 있다(구글링 하면 많이 나옴)

@JoinColumn은 외래 키를 매핑 할 때 사용된다.

School 에서 Student로 접근 할 수 있게 하려면 객체를 양방향으로 매핑해야한다.

@Entity
public class Student{
	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="STUDENT_ID")
	private Long id;

	@ManyToOne
	@JoinColumn(name="SCHOOL_ID")
	private School school;
}

@Entity
public class School{
	@Id
	@Column(name="SCHOOL_ID")
	private Long id;
	
	@OneToMany(mappedBy="school")
	private List<Student> students = new ArrayList<Student>();
}

요런식으로 할 수 있다.

mappedBy 속성은 반대편의 매핑 필드 이름을 값으로 주면 된다.

연관관계의 주인

양방향으로 매핑할 때에 데이터베이스에 매핑된 필드는 외래 키가 될 하나의 컬럼 뿐이다. 외래 키를 갖게 될 테이블과 매핑된 엔티티가 연관관계의 주인이 된다.

mappedBy 속성이 있는 엔티티는 외래 키를 가지지 않는다. mappedBy의 값으로 입력한 필드가 속한 객체가 연관관계의 주인이 되고 외래 키를 갖도록 테이블과 매핑 된다.

연관관계의 주인만 데이터베이스 연관관계와 매핑되고 외래 키를 관리할 수 있다. 주인이 아닌 편은 읽기만 가능하다. 참고로 데이터베이스 테이블의 다대일, 일대다 관계에서는 항상 다 쪽이 외래 키를 가진다. 다 쪽인 @ManyToOne은 항상 연관관계의 주인이 되므로 mappedBy 속성이 없다.

양방향 연관관계의 주의점

주인이 아닌 쪽에만 값을 입력하면 안된다. 예) school01.getStudents().add(student01); 이렇게 하면 자바 컬렉션에는 저장이 되겠지만 데이터베이스 상에는 아무것도 저장되지 않는다. 연관관계의 주인만 외래 키의 값을 변경할 수 있다.

객체의 관점에서 프로그래밍 상의 이점을 위해서 외래 키를 가지는 쪽과 아닌 쪽 모두 값을 입력해 주는 것이 좋기는 하다. 

student01.setSchool(school01);

school01.getStudents().add(student01);

이렇게 해주는게 테스트 코드를 쓸 때 등을 고려하면 이롭다.

연관관계 편의 메소드

student01.setSchool(school01);

school01.getStudents().add(student01);

 

처럼 각각 호출하다 보면 실수할 수도 있다.

두 코드를 하나 같이 사용하면 좋다.

Student의 setSchool 같은 메소드를 이렇게 써주면 좋다.

public void setSchool(School school) {

	// 이전에 추가한 스쿨이 있을 경우 청산할 필요가 있다.
	if(this.school != null){
		this.school.getStudents().remove(this);
	}
	this.school = school;
	school.getStudents().add(this);
}

 

요론식으로 해 주면 setSchool 하나로 객체 상 양방향을 설정해 줄 수 있게 된다. 이것을 편의 메소드라고 한다.

이렇게하면 관계 맺은 객체끼리 양방향으로 자유롭게 객체 그래프 탐색을 할 수 있게 된다. 그러나 어찌 되었든 데이터베이스는 테이블의 외래 키 하나로만 처리를 한다는 점을 인식하고 있어야 한다.

아무튼 이런 종류의 어노테이션들을 써서 다양한 연관관계를 매핑할 수 있다.

*다대다

다대다관계의 경우 @ManyToMany 어노테이션을 이용할 수도 있지만 실무에서는 보통 연결 테이블을 사용해서 다대일 일대다 관계로 풀어내어 사용한다. 왜냐하면 다대다 관계에서는 보통 서로의 아이디 값 외에 다른 추가 정보가 필요하기 때문이다.

이 경우 연결 테이블도 엔티티와 매핑하여 잘 쓰면 된다.

누군가가 잘 정리해 놓은 이 포스팅을 참고하여 열심히 연결을 할 수있다. https://ict-nroo.tistory.com/127 (이 분도 이 책을 보고 정리하신 것 같다)

상속관계 매핑

관계형 데이터베이스에는 객체지향의 상속 개념이 없다. 대신 수퍼타입 서브타입 관계라는 모델링 기법이 있기는 하다. ORM에서의 상속관계 매핑은 객체의 상속 구조와 데이터베이스의 수퍼타입 서브타입 관계를 매핑하는 것이라고 한다.

수퍼타입 서브타입은 논리적인 모델이고 실제 테이블로 구현할 때는 3가지 방법이 있다고 한다.

  • 각각의 테이블로 변환
  • 통합 테이블로 변환
  • 서브타입 테이블로 변환

조인 전략

각각의 테이블로 변환

엔티티 각각 테이블로 만들고 자식 테이블이 부모 테이블의 기본 키를 받아서 기본 키 + 외래 키로 사용하는 전략

사용되는 어노테이션

부모

@Inheritance(strategy = InheritanceType.JOINED)

-> 조인 전략을 사용한다는 뜻

@DiscriminatorColumn(name="DTYPE")

-> 테이블에는 객체 처럼 타입이 없으므로 객체의 타입에 대한 정보를 저장할 컬럼이 있어야 한다(구분 컬럼). 기본 값이 DTYPE이므로 name 옵션이 없어도 된다. 

자식

@DisciminatorValue("TYPE_A")

-> 자식테이블의 구분 컬럼에 들어갈 값을 입력, 부모를 extands 하는 엔티티에 달아준다. 

@PrimaryKeyJoinColumn(name="TYPE_A_ID")

-> 이 어노테이션이 없다면 디폴트로 부모의 키 이름을 그대로 쓰게 되지만 이게 있으면 이름을 따로 지정해 줄 수 있다.

장점 :

  • 테이블이 정규화된다.
  • 외래 키 참조 무결성 제약조건을 활용할 수 있다.
  • 저장공간을 효율적으로 사용한다.

 

단점:

  • 조회할 때 조인이 많이 사용되므로 성능이 저하될 수 있다.
  • 조회 쿼리가 복잡하다.
  • 데이터를 등록할 INSERT SQL을 두 번 실행한다.

코드 예

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="DTYPE")
public abstract class Item {
	@Id @GeneratedValue
	@Column(name = "ITEM_ID")
	private Long id;
	private String name;

	.....etc
}

@Entity
@DiscriminatorValue("A")
@PrimaryKeyJoinColumn(name ="ARTIST_ID")
public class Album extands Item {
	private String artist;
	
	......etc
}

단일 테이블 전략

이름 그대로 테이블을 하나만 사용한다. 구분 컬럼으로 어떤 자식 데이터가 저장되었는지를 구분한다. 조회할 때 조인이 필요없다.

이 경우 자식이 채우는 데이터 외에는 null 값이 들어갈 수 있어야 한다.

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)//전략만 바꿔주묜 댄다
@DiscriminatorColumn(name="DTYPE")
public abstract class Item {
	@Id @GeneratedValue
	@Column(name = "ITEM_ID")
	private Long id;
	private String name;

	.....etc
}

@Entity
@DiscriminatorValue("A")
@PrimaryKeyJoinColumn(name ="ARTIST_ID")
public class Album extands Item {
	private String artist;
	
	......etc
}

상속 전략만 바꿔주면 알아서 테이블 하나에 때려박아 준다고 한다.

장점 : 조인 쿼리가 없으므로 조회 성능이 좋다. 그리고 단순하다.

단점 : 컬럼이 nullable 해야한다. 테이블이 넘모 커질 수 있다.

구현 클래스마다 테이블 전략

InheritanceType.TABLE_PER_CLASS 로 바꿔주고 구분 컬럼 관련 어노테이션을 다 없애주면 된다.

구분컬럼을 사용하지 않고 그냥 다 때려 만든다.

장점 : 서브 타입을 구분해서 처리할 때 효과적

단점 : 아무도 추천 안함

@MappedSuperclass

매핑 정보를 상속만 하고 실제 테이블과 매핑하지 않고 싶을 경우 사용한다고 한다. 추상 객체로 만들어서 자식들이 상속하면 매핑되는 필드들을 그대로 사용할 수 있다(Base Entity 전략)

이 경우 @AttributeOverride로 물려 받은 매핑 정보를 재정의 하거나 @AssociationOverride, @AssociationOverrides 로 연관관계를 재정의 할 수도 있다.

테이블과는 관계가 없고 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모아주는 역할을 한다.

등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리할 수 있다.

 

 

'메모장/JPA' 카테고리의 글 목록

프로그래밍 & 일상

wonmocyberschool.tistory.com

 

'프로그래밍 > JPA' 카테고리의 다른 글

[JPA] 스프링 데이터 JPA  (0) 2021.05.29
[JPA] 객체지향 쿼리 언어  (0) 2021.05.29
[JPA] Embedded Type  (0) 2021.05.28
[JPA] 프록시와 지연로딩, CASCADE  (0) 2021.05.28
[JPA] JPA소개와 JPA의 Persistence Context  (0) 2021.05.23

댓글