내용
- 상속 관계 매핑: 객체의 상속 관계를 데이터베이스에서 매핑하는 방법
@MappedSuperclass: 등록일, 수정일 같이 여러 엔티티에서 공통으로 사용하는 매핑 정보만 상속받고 싶을 때 사용.- 복합키와 식별 관계 매핑: 데이터베이스의 식별자가 하나 이상일 때 매핑하는 방법 및 데이터베이스 설계의 식별관계와 비식별 관계
- 조인 테이블: 외래 키 이외에 연관 테이블을 두어 연관관계를 연결하는 방법. 연결 테이블을 매핑하는 방법을 다룸
- 엔티티 하나에 여러 테이블 매핑
관계형 데이터베이스에는 상속이라는 개념이 존재하지 않음. 위의 모델링 기법이 그나마 객체의 상속 개념과 가장 유사하다.

즉, ORM에서의 상속 관계 매핑은 객체의 상속 구조와 데이터베이스 슈퍼타입 서브타입 관계를 매핑하는 것.
엔티티 각각을 모두 테이블로 만들고 자식 테이블이 부모 테이블을 기본 키를 받아 "기본키 + 외래키"로 사용하는 전략. 따라서 조회 시 조인을 자주 사용한다.
해당 전략 사용 시 주의점: 객체는 타입으로 구분이 가능하지만, 테이블은 타입을 구분하지 못하기에 타입 구분 컬럼 추가 필요. DTYPE 컬럼을 구분 컬럼으로 사용.
//슈퍼타입 테이블
@Entity
@Interitance (strategy = InheritanceType.JOINED)
@DiscriminatorColumn (name = "DTYPE")
public abstract class Item {
@Id @GeneratedValue
@Column (name = "ITEM_ID")
private Long id;
prviate String name;
private int price;
}
//서브타입 테이블
@Entity
@DiscriminatorValue("A")
public class Album extends Item {
private String artist;
}
//서브타입 테이블
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {
private String director;
private String actor;
}
@Inheritance (strategy = InheritanceType.JOINED): 상속 매핑은 부모 클래스에 @Inheritance를 사용. 매핑 전략 지정에서 위에서는 조인 전략을 사용하므로 Inheritance.JOINED 선택.@DiscriminatorColumn(name="DTYPE"): 부모 클래스에 구분 컬럼을 지정. 해당 컬럼으로 자식 테이블을 구분.@DiscriminatorValue("M"): 엔티티 저장 시 구분 컬럼에 입력할 값을 지정.기본적으로 자식 테이블을 부모 테이블의 ID 컬럼명을 그대로 사용하게 되는데, 만약 이를 변경하기 위해서는 @PrimaryKeyJoinColumn을 사용.
@Entity
@DiscriminatorValue("B")
@PrimaryKeyJoinColumn(name = "BOOK_ID") //ID 재정의
public class Book extends Item {
private String author;
private String isbn;
}
조인전략 정리
장점:
- 테이블의 정규화
- 외래키 참조 무결성 제약조건 활용 가능
- 저장공간의 효율적 사용
단점:
- 조회 시 조인이 많이 사용되어 성능이 저하될 수 있음
- 조회 쿼리가 복잡함
- 데이터 등록 시 INSERT SQL을 두 번 실행해야함
특징:
- JPA 표준 명세는 구분 컬럼을 사용하도록 하지만, 하이버네이트 포함 몇몇 구현체는 구분 컬럼 없이도 동작
관련 어노테이션:
@PrimaryKeyJoinColumn,@DiscriminatorColumn,@DiscriminatorValue
테이블을 하나만 사용하며, 구분 컬럼으로 어떤 자식 데이터가 저장되었는지 구분. 조회 시 조인을 사용하지 않기 때문에 일반적으로 가장 빠른 편.
자식 엔티티가 매핑한 컬럼은 모두 널(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;
private int price;
}
@Entity
@DiscriminatorValue("A")
public class Album extends Item {...}
@Entity
@DiscriminatorValue("M")
public class Movie extends Item {...}
@Entity
@DiscriminatorValue("B")
public class Book extends Item {...}
InheritanceType.SINGLE_TABLE로 지정 시 단일 테이블 전략을 사용한다는 의미로 테이블 하나에 모든 것을 통합하기 때문에 구분 컬럼을 필수로 사용해야함.
단일테이블 전략 정리
장점:
- 조인이 필요없기에 일반적으로 조회 성능이 빠름
- 조회 쿼리가 단순함
단점:
- 자식 엔티티가 매핑한 컬럼은 모두 null을 허용해야함
- 단일 테이블에 모든 것을 저장하기에 테이블이 커질 수 있음. 따라서 상황에 따라 조회 성능이 떨어질 수 있음
특징:
- 구분 컬럼을 반드시 사용해야함.
@DiscriminatorColumn을 꼭 설정해야함@DiscriminatorValue를 지정하지 않을 시, 기본으로 엔티티 이름을 사용함
자식 엔티티마다 별개의 테이블을 만들고, 자식 테이블 각각에 필요한 컬럼이 모두 존재하는 방식이다.
@Entity
@Inheritance (strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
}
@Entity
public class Album extends Item {...}
@Entity
public class Movie extends Item {...}
@Entity
public class Book extends Item {...}
InheritanceType.TABLE_PER_CLASS로 지정하여 구현 클래스마다 테이블 전략을 사용. 해당 전략은 자식 엔티티마다 테이블을 만든다. 일반적으로는 추천하지 않는 전략이라고 한다.
구현 클래스마다 테이블 전략 정리
장점:
- 서브타입을 구분해 처리 시 효과적
not null제약 조건을 사용할 수 있음단점:
- 여러 자식 테이블을 함께 조회할 때 성능이 느림. (UNION 사용 필요)
- 자식 테이블을 통합해서 쿼리하기 어려움
특징
- 구분 컬럼을 사용하지 않음
@MappedSuperclass@MappedSuperclass앞서 부모클래스와 자식 클래스를 모두 데이터베이스 테이블과 매핑했는데, 만약 부모 클래스의 테이블과 매핑하지 않고, 부모 클래스를 상속받는 자식 클래스에게 매핑정보만 제공할 때는 @MappedSuperclass를 사용하면 됨.
비유하자면 추상클래스와 비슷하고 @Entity 어노테이션이 실제 테이블과 매핑되는 반면, @MappedSuperclass의 경우 실제 테이블과 매핑되지 않는다는 특징이 있다. 즉, 이는 단순히 매핑 정보를 상속할 목적으로만 사용됨.
@MappedSuperclass
public abstract class BaseEntity {
@Id @GeneratedValue
private Long id;
private String name;
}
@Entity
public class Member extends BaseEntity {
private String email;
}
@Entity
public class Seller extends BaseEntity {
private String shopName;
}
BaseEntity에 객체들의 공통 매핑 정보를 정의하고, 자식 엔티티들은 상속을 통해 BaseEntity의 매핑 정보를 물려받는다.
이때 BaseEntity는 테이블과 매핑할 필요 없고, 자식 엔티티에게 공통으로 사용되는 매핑 정보만 제공하면 되기에 @MappedSuperclass를 사용한다.
@AttributeOverrides, @AssociationOverrides부모로부터 물려받은 매핑 정보를 재정의하기 위해서 @AttributeOverrides를 사용하며, 연관관계를 재정의할 때는 @AssociationOverrides를 사용한다.
@Entity
@AttributeOverrides({
@AttributeOverride(name="id", column=@Column(name ="MEMBER_ID")),
@AttributeOverride(name="name", column=@Column(name="MEMBER_NAME"))
})
@MappedSuperclass로 지정한 클래스는 엔티티가 아니므로 em.find()나 JPQL에서 사용할 수 없음등록일자, 수정일자, 등록자, 수정자 같은 여러 엔티티에서 공통으로 사용하는 속성을 효과적으로 관리할 수 있다.
두 관계는 외래키가 기본키에 포함되는지의 여부에 따라 식별 관계와 비식별관계로 구분할 수 있다.
부모 테이블의 기본키를 내려받아 자식 테이블의 기본키+외래키로 사용하는 관계
부모 테이블의 기본키를 받아서 자식 테이블의 외래키로만 사용하는 관계
이 비식별 관계는 외래키에 널(NULL)을 허용하는지에 따라 다시 비식별 관계와 선택적 비식별 관계로 나뉨.
최근에는 비식별 관계를 주로 사용하고 반드시 필요한 곳에만 식별관계를 사용하는 추세로 JPA는 둘 모두 지원한다.
기본키를 구성하는 컬럼이 하나일 때
@Entity
public class Hello {
@Id
private String id;
}
둘 이상의 컬럼으로 구성된 복합 기본키의 경우, 식별자를 둘 이상 사용하기 위해서는 식별자 클래스를 별도로 만들어야한다.
이는 JPA가 영속성 컨텍스트에 엔티티를 보관할 때 엔티티의 식별자 키로 사용하는데, 이 식별자 구분을 위해 equals와 hashCode를 사용해 동등성 비교를 하기 때문이다. 식별자 필드가 2개 이상이면 별도 식별자 클래스를 만들고 거기에 이 동등성 비교를 구현해야한다.
이런 복합키를 위해 JPA는 @IdClass와 @EmbeddedId 2가지 방법을 제공한다. @IdClass는 관계형 데이터베이스에 가까운 방법이고, @EmbeddedId가 객체지향에 가까운 방법이라고한다.
@IdClass@Entity
@IdClass(ParentId.class)
public class Parent {
@Id
@Column (name = "PARENT_ID1")
private String id1;
@Id
@Column (name = "PARENT_ID2")
private String id2;
private String name;
}
각각의 기본키 컬럼을 @Id로 매핑한 후, @IdClass를 이용해 각 클래스를 식별자 클래스로 지정함.
public class ParentId implements Serializable {
private String id1;
private String id2;
public ParentId() {}
public ParentId(String id1, String id2) {
this.id1 = id1;
this.id2 = id2;
}
@Override
public boolean equals (Object o) {...}
@Override
public boolean equals hashCode () {...}
}
@IdClass 사용 시 식별자 클래스는 아래 조건을 만족해야함.
Serializable 인터페이스를 구현해야함equals, hashCode를 구현해야함Parent parent = new Parent();
parent.setId1("myId1");
parent.setId2("myId2");
parent.setName("parentName");
em.persist(parent);
식별자 클래스가 없어도 em.persist() 호출 시 영속성 컨텍스트에 엔티티를 등록하기 전 내부에서 Parent.id1, Parent.id2 값을 사용해 식별자 클래스를 생성하고 영속성 컨텍스트의 키로 사용하게 됨.
복합키로 조회할 경우 아래와 같이 이루어진다.
ParentId parentId = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.clss, parentId);
식별자 클래스를 통해서 엔티티를 조회할 수 있다.
자식 클래스를 추가하는 코드는 아래와 같다.
@Entity
public class Child {
@Id
private String id;
@ManyToOne
@JoinColumns ({
@JoinColumn(name = "PARENT_ID1", referencedColumnName = "PARENT_ID1"),
@JoinColumn(name = "PARENT_ID2", referencedColumnName = "PARENT_ID2")
})
private Parent parent;
}
부모 테이블의 기본키 컬럼이 복합키이므로 자식 테이블의 외래키도 복합키가 된다. 즉, 외래키 매핑 시 여러 컬럼을 매핑해야하므로 @JoinColumns 어노테이션을 사용해야하고 각각의 외래키를 @JoinColumn으로 매핑해야한다.
@EmbededId@Entity
public class Parent {
@EmbeddedId
private ParentId id;
private String name;
}
Parent 엔티티에서 식별자 클래스를 직접 사용하고 @EmbeddedId 어노테이션을 적어주면 된다. 식별자 클래스는 아래와 같다.
@Embeddable
public class ParentId implements Serializable {
@Column (name = "PARENT_ID1")
private String id1;
@Column (name = "PARENT_ID2")
private String id2;
//equals와 hashcode 구현
}
@EmbeddedId를 적용한 식별자 클래스의 경우에는 식별자 클래스에 기본키를 직접 매핑하게 된다.
@EmbeddedId를 적용한 식별자 클래스의 경우 아래 조건을 만족해야한다.
@Embeddable 어노테이션을 붙여줘야한다Serializable 인터페이스를 구현해야한다엔티티를 저장하는 코드를 보면 아래와 같다
Parent parent = new Parent();
ParentId parentId = new ParentId("myId1", "myId2");
parent.setId(parentId);
parent.setName("parentName");
em.persist(parent);
엔티티를 조회하는 코드는 역시 parentId (식별자 클래스)를 직접 사용한다.
ParentId parentId = new ParentId("myId1", "myId2");
Parent parent = em.find(Parent.class, parentId);
equals(), hashCode()복합키를 사용할 때는 equals()와 hashCode()를 필수적으로 구현해야한다.
ParentId id1 = new parentId();
id1.setId1("myId1");
id2.setId2("myId2");
ParentId id2 = new parentId();
id2.setId2("myId1");
id2.setId2("myId2");
id1.equals(id2);
위의 경우 id1과 id2 인스턴스 둘 모두 같은 값을 가짐에도 다른 인스턴스이기에 equals()를 사용하였을 때 거짓이 된다. 이는 기본으로 제공되는 equals()는 인스턴스 참조값 비교(동일성 비교)를 하기 때문이다.
영속성 컨텍스트는 엔티티의 식별자를 키로 사용해 엔티티를 관리하기에 동등성 비교가 지켜지지 않으면 예상과 다른 엔티티가 조회되거나 엔티티를 찾을 수 없는 문제가 발생하기에 복합키에서의 equals()와 hashCode() 구현은 필수적이다.
+) 복합키에는 GeneratedValue를 사용할 수 없다. 복합키를 구성하는 여러 컬럼 중 하나에도 역시 마찬가지이다.
식별관계에서 자식 테이블은 부모 테이블의 기본키를 포함해 복합키를 구성해야하기에 @IdClass나 @EmbeddedId를 사용해 식별자를 매핑해야함.
@IdClass와 식별관계//부모
public class Parent {
@Id @Column (name = "PARENT_ID")
private String id;
private String name;
}
//자식
@Entity
@IdClass(ChildId.class)
public class Child{
@Id
@ManyToOne
@JoinColumn (name = "PARENT_ID")
public Parent parent;
@Id @Column(name = "CHILD_ID")
private String childId;
private String name;
}
//자식 ID
public class ChildId implements Serializable {
private String parent; //Child.parent 매핑
private String childId; //Child.childId 매핑
//equals, hashCode 매핑
}
//손자
@Entity
@IdClass(GrandChildId.class)
public class GrandChild {
@Id
@ManyToOne
@JoinColumns ({
@JoinColumn(name = "PARENT_ID"),
@JoinColumn(name = "CHILD_ID")
})
private Child child;
@Id @Column(name = "GRANDCHILD_ID")
private String id;
private String name;
}
//손자ID
public class GrandChildId implements Serializable {
private ChildId child; //GrandChild.child 매핑
private String id; //GrandChild.id 매핑
//equals, hashCode 매핑
}
식별 관계는 기본키와 외래키를 같이 매핑해야함. 따라서 식별자 매핑인 @Id와 연관관계 매핑인 @ManyToOne을 함께 사용해야한다.
@Id
@ManyToOne
@JoinColumn(name = "PARENT_ID")
public Parent parent;
Child 엔티티의 parent 필드를 보면 @Id를 통해 기본키로 매핑하면서 동시에 @ManyToOne과 @JoinColumn으로 외래키를 같이 매핑한다.
@EmbeddedId와 식별 관계@EmbeddedId로 식별 관계를 구성할 땐 @MapsId를 사용한다.
//부모
@Entity
public class Parent {
@Id @Column (name = "PARENT_ID")
private String id;
private String name;
}
//자식
@Entity
public class Child {
@EmbeddedId
private ChildId id;
@MapsId("parentId") //ChildId.parentId 매핑
@ManyToOne
@JoinColumn (name = "PARENT_ID")
public Parent parent;
private String name;
}
//자식 ID
@Embeddable
public class ChildId implements Serializable {
private String parentId; //@MapsId("parentId")로 매핑
@Column(name = "CHILD_ID")
private String id;
//equals, hashCode ...
}
//손자
@Entity
public class GrandChild {
@EmbededId
private GrandChild id;
@MapsId("childId") //GrandChildId.childId 매핑
@ManyToOne
@JoinColumns({
@JoinColumn(name="PARENT_ID"),
@JoinColumn(name="CHILD_ID")
})
private Child child;
private String name;
}
//손자ID
@Embeddable
public class GrandChildId implements Serializable {
private ChildId childId; //@MapsId("childId")로 매핑
@Column(name = "GRANDCHILD_ID")
private String id;
//equals, hashCode...
}
@MapsId는 외래키와 매핑한 연관관계를 기본키에도 매핑한다는 의미이다. @MapsId의 속성값은 @EmbeddedId를 사용한 식별자 클래스의 기본키 필드를 지정하면 된다.
@Entity
public class Parent {
@Id @GeneratedValue
@Column (name = "PARENT_ID")
private Long id;
private String name;
}
@Entity
public class Child {
@Id @GeneratedValue
private String name;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
}
//손자
@Entity
public class GrandChild {
@Id @GeneratedValue
@Column(name = "GRANDCHILD_ID")
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "CHILD_ID")
private Child child;
}
복합키가 없으니 복합키 클래스를 만들지 않아도 된다.
일대일 식별관계의 경우, 자식테이블의 기본키 값으로 부모테이블의 기본키값 만을 사용한다. 따라서 부모테이블의 기본키가 복합키가 아니라면, 자식 테이블의 기본키를 복합키로 따로 구성할 필요가 없다.
//부모
@Entity
public class Board {
@Id @GeneratedValue
@Column(name = "BOARD_ID")
private Long id;
private String title;
@OneToOne(mappedBy = "board")
private BoardDetail boardDetail;
}
//자식
@Entity
public class BoardDetail{
@Id
private Long boradId;
@MapsId //BoardDetail.boardId 매핑
@OneToOne
@JoinColumn (name ="BOARD_ID")
private Board board;
private String content;
}
BoardDetail처럼 식별자가 단순히 컬럼 하나일 경우 @MapsId를 사용하고 속성값은 비워두면 된다. 이때 @MapsId는 @Id를 사용해 식별자로 지정한 BoardDetail.boardId와 매핑되게 된다.
public void save(){
Board board = new Board();
board.setTitle("제목")
em.persist(board);
BoardDetail boardDetail = new BoardDetail();
boardDetail.setContent("내용");
boardDetail.setBoard(board);
em.persist(boardDetail);
}
데이터베이스 설계 관점에서는 비식별 관계를 더 선호한다.
객체 관계 매핑의 관점에서는 아래 이유로 비식별 관계를 선호한다.
@GeneratedValue 처럼 대리키 생성을 위한 편리한 방법을 제공한다.식별 관계를 사용할 때의 장점도 존재한다. 기본키 인덱스를 활용하기 좋고, 상위 테이블들의 기본키 컬럼을 자식, 손자 테이블들이 가지고 있기에 특정 상황에서는 조인 없이 하위 테이블만으로도 검색을 완료할 수 있다.
데이터베이스 테이블의 연관관계 설계 방법 2가지.
예) 회원과 사물함이 있을 때, 각 테이블에 데이터를 등록했다가, 회원이 원할 때 사물함을 선택할 수 있다.
조인 테이블의 경우 연관관계를 관리하는 조인 테이블을 추가하고, 두 테이블의 외래키를 가지고 연관관계를 관리한다. 즉, 회원과 사물함에는 연관관계를 관리하기 위한 외래키 컬럼이 존재하지 않는다.
회원과 사물함 데이터를 각각 등록하고, 회원이 원할 때 사물함을 선택하면 조인 테이블에만 값을 추가하면 된다.
조인 테이블의 단점은 테이블을 하나 추가해야한다는 점으로 관리해야하는 테이블이 늘어나고, 회원과 사물함 테이블을 조인하기 위해 조인 테이블까지 추가로 조인해야한다는 단점이 있다.
따라서 기본은 조인 컬럼을 사용하고 필요 시에만 조인 테이블을 사용하는 것이 권장된다.
일대일 관계를 만들기 위해서는 조인 테이블의 외래키 컬럼 각각에 총 2개의 유니크 제약조건을 걸어야한다.
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToOne
@JoinTable(name = "PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name="CHILD_ID")
)
private Child child;
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column (name = "CHILD_ID")
private Long id;
private String name;
}
부모 엔티티의 경우 @JoinColumn을 대신해 @JoinTable을 사용함.
@JoinTable의 속성양방향으로 매핑하기 위해서는 아래의 코드를 추가하면 된다.
public class Child {
@OneToOne(mappedBy="child")
private Parent parent;
}
일대다 관계를 만들기 위해서는 조인 테이블의 컬럼 중 다와 관련된 커럼인 CHILD_ID에 유니크 제약조건을 걸어야한다. 일대다 단방향 관계로 매핑하면 아래와 같다.
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@OneToMany
@JoinTable(name="PARENT_CHILD",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseJoinColumns = @JoinColumn(name = "CHILD_ID")
)
private List<Child> child = new ArrayList<Chlid>();
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
}
다대일의 경우 일대다에서 방향만 반대일 뿐, 조인 테이블의 모양은 일대다와 같다.
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column (name = "PARENT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "parent")
private List<Child> child = new ArrayList<>();
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
@ManyToOne(optional = false)
@JoinTable (name = "PARENT_CHILD",
joinColumns = @JoinColumn(name ="CHILD_ID"),
inverseColumns = @JoinColumn(name="PARENT_ID")
private Parent parent;
}
다대다 관계를 만들기 위해서는 조인테이블의 두 컬럼을 합해서 하나의 복합 유니크 제약조건을 걸어야한다.
//부모
@Entity
public class Parent {
@Id @GeneratedValue
@Column(name = "PARENT_ID")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "PARENT_CHLID",
joinColumns = @JoinColumn(name = "PARENT_ID"),
inverseColumns = @JoinColumn(name = "CHILD_ID")
)
private List<Child> child = new ArrayList<>();
}
//자식
@Entity
public class Child {
@Id @GeneratedValue
@Column(name = "CHILD_ID")
private Long id;
private String name;
}
조인 테이블에 다른 컬럼을 추가하게 되면 @JoinTable 전략을 사용할 수 없게된다. 대신 새로운 엔티티를 만들어서 조인 테이블과 매핑해야한다.
@SecondaryTable을 사용하면 하나의 엔티티에 여러 테이블을 매핑할 수 있다. (다만, 잘 사용하지는 않는다고 한다.
@Entity
@Table(name ="BOARD")
@SecondaryTable (name = "BOARD_DETAIL",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "BOARD_DETAIL_ID"))
public class Board{
@Id @GeneratedValue
@Column (name = "BOARD_ID")
private Long id;
private String title;
@Column (table = "BOARD_DETAIL")
private String content;
}
Board 엔티티의 경우 @Table을 통해 BOARD 테이블과 매핑한 후, @SecondaryTable을 통해 BOARD_DETAIL 테이블을 추가로 매핑한다.
@SecondaryTable 속성.name: 매핑할 다른 테이블의 이름..pkJoinColumns: 매핑할 다른 테이블의 기본키 컬럼 속성.다만 최적화를 위해서는, 여러 테이블을 하나의 엔티티에 매핑하는 것보다는 테이블 당 엔티티를 각각 만들어 일대일 매핑하는 것이 권장된다.