Whiteship's Note


Hibernate VS JPA

Hibernate/Chapter 7 : 2008.02.20 18:33


Hibernate JPA
One-To-One 연관 맵핑에서 공유하는 주키의 자동 생성을 지원한다. 표준화 된 One-To-One 맵핑을 지원한다. 하이버네이트 확장을 이용하면 공유하는 주키를자동 생성할 수 있다.
하이버네이트는 모든 엔티티 연관 맵핑을 Join Table로 할 수 있다. 표준화된 연관 맵핑은 2차 테이블을 사용해서 가능하다.
하이버네이트는 인덱스를 가진 엔티티 리스트를 맵핑할 수 있다. 하이버네이트 확장 애노테이션이 있어야 가능하다.
하이버네이트는 모든 경우에 다형적인 연관을 지원한다. Table Per Concrete Class With Implicit Polymorphism은 빼고는 전부 지원한다.
top

Write a comment.


Table Per Concrete Class(with implicit polymorphism)에서 다형적인 연관

Hibernate/Chapter 7 : 2008.02.20 18:32


특징

  • Table Per Concrete Class With Implicite Polymorphism으로 맵핑했을 경우를 살펴본다.
  • 상위 타입과 OneToMany 콜렉션으로 맵핑할 수 없다.
  • 그래도 다형적인 쿼리를 사용하려면 핵을 사용해야 하는데, 이건 정말이지.. 최후의 선택이어야만 한다. 이러기 전에 union으로 설정을 바꾸는 것을 고려해보아라.
  • XML 설정에서는 핵을 사용해서 억지로 다형적인 연관을 맺게 할 수 있지만, SQL 테이블 조인식을 작성하기도 어려워지고, HQL도 그런 연관 맵핑을 지원하지 않는다. 그리고 JPA 애노테이션도 그런 설정을 지원하지 않는다.
  • 절대로 사용하지 말자. 따라서 패스.
top

Write a comment.


Union을 사용한 다형적인 연관

Hibernate/Chapter 7 : 2008.02.20 18:31


특징

  • Table Per Subclass와 Table Per Class Hierarchy에서의 다형적인 연관을 확인했다.
  • Table Per Concrete Class With Union에서도 똑같이 적용된다.
콜렉션 테스트
@Test
public void oneTomanyConfirm() throws Exception {
Session session = sessionFactory.openSession();

User user = (User) session.get(User.class, 1l);
Set<BillingDetails> bds = user.getBilllingDetailses();
for(BillingDetails bd : bds){
System.out.println("..");
}

session.flush();
session.close();
}
단일 클래스 연관 테스트
@Test
public void oneToOneConfirm() throws Exception {
Session session = sessionFactory.openSession();

User user = (User) session.get(User.class, 1l);
BillingDetails bd = user.getDefaultBillingDetails();
bd.pay();

session.flush();
session.close();
}
  • @OneToOne 이나 @ManyToOne으로 상위 타입과 연관을 맺고 있을 때, 외례키 제약은 하이버네이트가 만들어 줄 수 없다. 왜? 어떤 하위 클래스의 주키를 외례키로 가져야 하는지 판단하는게 쉽지 않다. 따라서 사용자가 적당한 제약 사항을 만들어야 한다.
top

Write a comment.


다형적인 콜렉션 연관

Hibernate/Chapter 7 : 2008.02.20 18:30


특징

  • Table Per Class Hierachy 또는 Table Per Subclass를 사용한 상속구조로 맵핑 했다면, 다음과 같이 해도 아무일 없다.
저장하기
CreditCard cc = new CreditCard();
cc.setNumber(ccNumber);
cc.setType(ccType);
cc.setExpMonth(...);
cc.setExpYear(...);
User user = (User) session.get(User.class, userId);
// Call convenience method that sets both sides of the association
user.addBillingDetails(cc);
// Complete unit of work
가져오기
User user = (User) session.get(User.class, userId);
for( BillingDetails bd : user.getBillingDetails() ) {
// Invoke CreditCard.pay() or BankAccount.pay()
bd.pay(paymentAmount);
}
  • @ManyToMany 와 @OneToMany의 기본 FetchMode는 LAZY다. 따라서, 위의 코드에서 이터레이터로 BillingDetails를 하나 가리키기 시작할 때 콜렉션을 가져온다.
  • 루프 돌지 않고, user.getBillingDetails() 호출하면, 쿼리는 날아가지 않는다.
top

Write a comment.


다형적인 ManyToOne 연관

Hibernate/Chapter 7 : 2008.02.20 18:28


특징

  • 다형적인 연관 관계는 Table per concrete class with unions, Table per class per hierachy, Table per subclass 모두 사용할 수 있다.
  • lazy=true로 설정했다면, 하위 타입으로 바로 캐스팅 할 수 없다. Proxy 객체를 가져왔기 때문이다.(XML 설정)
  • session.load()를 사용해서 해당 타입으로 다시 로딩하여 사용하거나, eager fetch query를 사용한다.
Proxy Narrowing
User user = (User) session.get(User.class, userId);
BillingDetails bd = user.getDefaultBillingDetails();
// Narrow the proxy to the subclass, doesn't hit the database
CreditCard cc =
(CreditCard) session.load( CreditCard.class, bd.getId() );
expiryDate = cc.getExpiryDate();
Eager Fetch Query
User user = (User)session.createCriteria(User.class)
.add(Restrictions.eq("id", uid) )
.setFetchMode("defaultBillingDetails", FetchMode.JOIN)
.uniqueResult();
// The users defaultBillingDetails have been fetched eagerly
CreditCard cc = (CreditCard) user.getDefaultBillingDetails();
expiryDate = cc.getExpiryDate();
  • OneToOne 연관도 이것과 같다.
  • 애노테이션을 사용한 맵핑에서는 기본 FetchMode가 EAGER다. 따라서 불필요한 쿼리가 생길 수도 있다.
top

Write a comment.


하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이

Hibernate/Chapter 7 : 2008.02.20 16:28


하이버 3.0부터는 lazy=true"가 기본값이라고 나와있습니다. (여기에.. Association fetching strategies 참조)

콜렉션이든 단일 클래스든 모두 Lazy Loading.

하지만 이 말은 XML 설정에 국한 된 말입니다. 애노테이션 설정에서는 그렇치 않습니다.
여기에 2.2.5.5. Association fetching 이 부분을 참조하면 다음과 같이 나와있습니다.

@OneToMany와 @ManyToMany 의 FetchMode는 Lazy
@OneToOne과 @ManyToOne의 FetchMode는 Eager

따라서, XML 설정으로 lazy="true"를 생각하고 코딩한 것을 애노테이션으로 옮길 때는 모두 명시적으로 fetch=FetchMode.LAZY로 설정해주어야 예상치 못한 쿼리가 발생하는 것을 방지할 수 있습니다.

예상치 못한 쿼리는 다음과 같이 발생합니다.
사용자 삽입 이미지

이런 상황(상속 구조는 Table Per Subclass로 맵핑)에서.. User가 가지고 있는 DefaultBillingDetails를 가져온다고 가정합니다. User와 DefaultBillingDetails는 @OneToObe 이나 @ManyToOne으로 맵핑할 수 있습니다. 그러면 default가 EAGER죠.

        User loadedUser = (User) session.get(User.class, 1l);
        assertNotNull(loadedUser);
        BillingDetails billingDetails = loadedUser.getDefaultBillingDetails();
        System.out.println("읽어올 때 타입을 알 수 있나? " + new Boolean(billingDetails instanceof CreditCard).toString());

- default 상황에서 발생하는 쿼리

    select
        user0_.id as id3_2_,
        user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_2_,
        user0_.name as name3_2_,
        billingdet1_.BILLING_DETAILS_ID as BILLING1_0_0_,
        billingdet1_.USER_ID as USER2_0_0_,
        billingdet1_1_.NUMBER as NUMBER1_0_,
        billingdet1_2_.ACCOUNT as ACCOUNT2_0_,
        case
            when billingdet1_1_.BILLING_DETAILS_ID is not null then 1
            when billingdet1_2_.BILLING_DETAILS_ID is not null then 2
            when billingdet1_.BILLING_DETAILS_ID is not null then 0
        end as clazz_0_,
        user2_.id as id3_1_,
        user2_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_1_,
        user2_.name as name3_1_
    from
        User user0_
    left outer join
        BILLING_DETAILS billingdet1_
            on user0_.DEFAULT_BILLING_DETAILS_ID=billingdet1_.BILLING_DETAILS_ID
    left outer join
        CREDIT_CARD billingdet1_1_
            on billingdet1_.BILLING_DETAILS_ID=billingdet1_1_.BILLING_DETAILS_ID
    left outer join
        BANK_ACCOUNT billingdet1_2_
            on billingdet1_.BILLING_DETAILS_ID=billingdet1_2_.BILLING_DETAILS_ID
    left outer join
        User user2_
            on billingdet1_.USER_ID=user2_.id
    where
        user0_.id=?

User와 User가 가지고 있는 BillingDetails를 left outter join으로 가져옵니다.
BillingDetails를 Table Per Subclass로 맵핑했기 떄문에 CREDIT_CARD와 BANK_ACCOUNT에 left ountter join을 사용하여 모든 레코드를 가져옵니다.
마지막으로 BillingDetails가 ManyToOne으로 참조하는 User를 가져옵니다.(이건 User의 DefaultBillingDetail과 연관을 맺고 있는 User가 아닙니다. 그런 속성은 없습니다. 이 속성은 User가 가지고 있는 BillingDetail 콜렉션과 양방향으로 연관을 맺고 있는 속성입니다.)

굉장한 쿼리가 만들어졌습니다. User가 defaultBillingDetail을 가져와서 아직 사용하지도 않았는데 말이죠.

- @OneToOne 또는 @ManyToOne에서 fetch=FetchMode.LAZY 로 설정한 경우

    select
        user0_.id as id3_0_,
        user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
        user0_.name as name3_0_
    from
        User user0_
    where
        user0_.id=?

딱 User의 정보만 가져왔습니다. 분명 가져오라고 했는데... 쿼리를 날리지 않았습니다. 그러면 이 상태에서 BillingDetail에 있는 pay()라는 메소드를 실행하면 NullPointerException이 떨어질까요?

        User loadedUser = (User) session.get(User.class, 1l);
        assertNotNull(loadedUser);
        BillingDetails billingDetails = loadedUser.getDefaultBillingDetails();
        System.out.println("읽어올 때 타입을 알 수 있나? " + new Boolean(billingDetails instanceof CreditCard).toString());
        billingDetails.pay();
        System.out.println("이제는 읽어올 때 타입을 알 수 있을까? " + new Boolean(billingDetails instanceof CreditCard).toString());

이렇게 해봤습니다.

Hibernate:
    select
        user0_.id as id3_0_,
        user0_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
        user0_.name as name3_0_
    from
        User user0_
    where
        user0_.id=?
읽어올 때 타입을 알 수 있나? false
Hibernate:
    select
        billingdet0_.BILLING_DETAILS_ID as BILLING1_0_1_,
        billingdet0_.USER_ID as USER2_0_1_,
        billingdet0_1_.NUMBER as NUMBER1_1_,
        billingdet0_2_.ACCOUNT as ACCOUNT2_1_,
        case
            when billingdet0_1_.BILLING_DETAILS_ID is not null then 1
            when billingdet0_2_.BILLING_DETAILS_ID is not null then 2
            when billingdet0_.BILLING_DETAILS_ID is not null then 0
        end as clazz_1_,
        user1_.id as id3_0_,
        user1_.DEFAULT_BILLING_DETAILS_ID as DEFAULT3_3_0_,
        user1_.name as name3_0_
    from
        BILLING_DETAILS billingdet0_
    left outer join
        CREDIT_CARD billingdet0_1_
            on billingdet0_.BILLING_DETAILS_ID=billingdet0_1_.BILLING_DETAILS_ID
    left outer join
        BANK_ACCOUNT billingdet0_2_
            on billingdet0_.BILLING_DETAILS_ID=billingdet0_2_.BILLING_DETAILS_ID
    left outer join
        User user1_
            on billingdet0_.USER_ID=user1_.id
    where
        billingdet0_.BILLING_DETAILS_ID=?

pay() 메소드를 호출하는 순간, BillingDetails 하나를 가져오고 해당 메소드를 호출해 줍니다.
top

Write a comment.


맵 맵핑하기

Hibernate/Chapter 7 : 2008.02.09 10:24


Entity를 참조하는 값

특징

  • Map<Long, BId> 처럼, Bid의 주키값을 맵의 키값으로 가지는 콜렉션으로 맵핑할 수 있다.
  • 애플리케이션에서만 다를 뿐, 테이블은 변한 거 없다. Bid에 ITEM_ID 외례키 컬럼이 생긴다.

맵핑하기

Item.java
@MapKey(name = "id")
@OneToMany(mappedBy="item")
private Map<Long, Bid> bids = new HashMap<Long, Bid>();
Bid.java
@ManyToOne
private Item item;

ternary association

특징

  • 3항 연관을 맵을 사용해서 표현할 수 있다.
  • Category, Item, User의 관계에서 Category가 Item을 키, User을 값으로 가지는 콜렉션을 가지도록 설정할 수 있다. 왜? Category에 속하는 Item은 유일하고, User는 여러 개의 Item을 추가할 수 있으니까.

맵핑하기

Category.java
@ManyToMany
@MapKeyManyToMany(joinColumns = @JoinColumn(name = "ITEM_ID"))
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"), inverseJoinColumns = @JoinColumn(name = "USER_ID"))
private Map<Item, User> itemsAndUser = new HashMap<Item, User>();
  • CATEGORY_ITEM 테이블을 만들고, 이 테이블의 주키를 CATEGORY_ID로 설정한다.
  • 맵의 키는 ITEM_ID, 값은 USER_ID로 맵핑된다.
top

Write a comment.


Join Table에 컬럼 추가하기

Hibernate/Chapter 7 : 2008.02.09 10:22


특징

  • 연관 테이블에 추가 속성이 필요할 수 있다.

연관 테이블을 Intermediate Entity로 맵핑하기

특징

  • 연관 테이블로 맵핑 될 새로운 테이블을 작성한다.
  • 양방향 네이게이션이 가능하다.(장점)
  • 연관 클래스를 생성하고 제거하는데 관리해야하는 코드가 늘어난다.(단점)
  • Category나 Item을 추가할 때 CategoryItem에 Cascade 옵션을 사용해서 transitive persistence를 사용할 수 있다.(12장에서 다룸)

맵핑하기

CategoryItem.java
@Entity
public class CategoryItem {

@Embeddable
public static class Id implements Serializable {

@Column(name = "CATEGORY_ID")
private Long categoryId;

@Column(name = "ITEM_ID")
private Long itemId;

public Id() {
}

public Id(Long categoryId, Long itemId) {
this.categoryId = categoryId;
this.itemId = itemId;
}

public boolean equals(Object o) {
if (o != null && o instanceof Id) {
Id that = (Id) o;
return this.categoryId.equals(this.categoryId)
&& this.itemId.equals(that.itemId);
} else {
return false;
}
}

public int hashCode() {
return categoryId.hashCode() + itemId.hashCode();
}
}

public CategoryItem() {
}

public CategoryItem(Category category, Item item) {
this.category = category;
this.item = item;

id.categoryId = category.getId();
id.itemId = item.getId();

item.getCategoryItems().add(this);
category.getCategoryItems().add(this);
}

@EmbeddedId
private Id id = new Id();

@Column(name = "ADDED_ON")
private Date dateAdded = new Date();

@ManyToOne
@JoinColumn(name = "ITEM_ID", insertable = false, updatable = false)
private Item item;

@ManyToOne
@JoinColumn(name = "CATEGORY_ID", insertable = false, updatable = false)
private Category category;

//getter, setter
}
Category.java
@OneToMany(mappedBy = "category")
private Set<CategoryItem> categoryItems = new HashSet<CategoryItem>();
  • 생성자에 참조 무결성을 지키기 위한 코드 추가.
  • 이 객체가 연관을 맺고 있는 클래스들의 상태는 Detached 이거나, Persistent 상태여야 한다. Transient 상태에서 Fake Object를 활용해도 되지 않는다.
Error Log
Hibernate: insert into CategoryItem (ADDED_ON, CATEGORY_ID, ITEM_ID) values (?, ?, ?)
2008-02-08 09:51:31,381 WARN [org.hibernate.util.JDBCExceptionReporter] -
<SQL Error: 0, SQLState: null>
2008-02-08 09:51:31,381 WARN [org.hibernate.util.JDBCExceptionReporter] -
<SQL Error: 0, SQLState: null>
2008-02-08 09:51:31,381 ERROR [org.hibernate.util.JDBCExceptionReporter] - <failed batch>
2008-02-08 09:51:31,381 ERROR [org.hibernate.util.JDBCExceptionReporter] - <failed batch>
2008-02-08 09:51:31,381 ERROR [org.hibernate.event.def.AbstractFlushingEventListener] -
<Could not synchronize database state with session>

연관 테이블을 컴포넌트의 콜렉션으로 맵핑하기

특징

  • 라이프사이클 관리를 별도로 하지 않아도 된다. 컴포넌트 콜렉션을 가지고 있는 Entity에 추가하거나 삭제하기만 하면 된다.
  • 양방향 네비게이션을 할 수 없다. 컴포넌트는 공유될 수 없기 때문에, Item에서 CategoryItem으로 이동하지 못한다. 그치만 SQL을 잘 작성해서 가져올 순 있다.

맵핑하기

CategoryItem.java
@Embeddable
public class CategoryItem {

public CategoryItem() {
}

public CategoryItem(Category category, Item item){
this.category = category;
this.item = item;
}

@Parent
private Category category;

@ManyToOne
@JoinColumn(name = "ITEM_ID", insertable = false, updatable = false)
private Item item;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "ADDED_ON", nullable = false)
private Date dateAdded = new Date();

// getter, setter, equals, hashcode
}
Category.java
@CollectionOfElements
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"))
private Set<CategoryItem> categoryItems = new HashSet<CategoryItem>();
  • 양방향 관계를 맵핑할 수 없다. Item에서 CategoryItems 콜렉션을 가질 수 없다. 오직 하나의 Entity에 포함된다. 공유 자원이 아니니까.
  • CategoryItem에 있는 모든 속성은 Not Null이어야 한다. 주키 없으니까. 모든 속성을 복합키로 사용하기 때문에.
top

Write a comment.


ManyToMany 관계 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:20


단방향 다대다 관계

  • 대부분 추가적인 정보가 필요하기 때문에, 별도의 assiation class를 만들게 된다. 여기서는 간단한 다대다 맵핑.

Set 타입으로 맵핑하기

Category.java
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private Set<Item> items = new HashSet<Item>();
  • 이 때는 Category 클래스에 별도의 컬럼이 생기지 않는다. @ManyToOne에서 Join Table을 만들었을 때는 inverseJoinColumns의 컬럼이 생겼었다.
  • 콜렉션에 추가할 때(category.getItems().add(itme)) 다음의 SQL 날린다. insert into CATEGORY_ITEM (CATEGORY_ID, ITEM_ID) values (?, ?)

Idbag 타입으로 맵핑하기

Category.java
@ManyToMany
@CollectionId(
columns=@Column(name="CATEGORY_ITEM_ID"),
generator = "sequence",
type = @Type(type="long")
)
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private Set<Item> items = new HashSet<Item>();
  • surrogate 키를 가지고 있기 때문데, 중복을 허용한다.
  • @CollectionId, @Type은 하이버네이트의 애노테이션이다.
  • 콜렉션에 추가할 때 다음의 SQL 날린다. insert into CATEGORY_ITEM (CATEGORY_ID, CATEGORY_ITEM_ID, ITEM_ID) values (?, ?, ?)

List 타입으로 맵핑하기

Category.java
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM", joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
@IndexColumn(name = "DISPLAY_POSITION")
private List<Item> items = new ArrayList<Item>();
  • 콜렉션에 추가할 때 다음의 SQL 날린다. insert into CATEGORY_ITEM (CATEGORY_ID, DISPLAY_POSITION, ITEM_ID) values (?, ?, ?)

양방향 다대다 관계

Item.java
@ManyToMany(mappedBy="items")
private Set<Category> categories = new HashSet<Category>();
  • 양방향 관계에서 1쪽의 inverse로 맵핑했었던 이유? 외례키 컬럼을 두 번이나 사용했으니까.
  • 이번에도 마찬가지로, 두 쪽 중에 한 쪽은 inverse로 설정해 주어야 한다.
  • cascade 설정 중에 all, delete, delete-orphans는 다대다 관계에서 의미가 없다. 왜? Value 타입이면 모를까, Entity 타입인데, 다른 Entity가 지워진다고 해서, 해당 Entity까지 지워지라는 보장은 없다. 거의 그런 경우는 없을 것이다. 따라서 의미가 없다. save or update만 사용하는게 좋겠다.
  • 양방향 관계에 있는 콜렉션 타입 결정은 inverse와 관련이 있다. inverse쪽에는 bag과 set처럼 id가 없는 것들만 올 수 있다. 왜? index나 키를 생성하는 쪽 SQL을 무시해버리면 indexColumn에 필요한 정보가 들어갈리 없으니까. 결론적으로, 양쪽 모두 index나 키를 가지는 콜렉션끼리 다대다 관계를 가질 수 없다. 왜? 양쪽 중 한 쪽은 inverse로 설정할 텐데 그러면 그 쪽에 필요한 정보가 생성되지 않을테니까.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 콜렉션 연관  (0) 2008.02.20
다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
top

  1. seeyoung 2008.03.18 22:08 PERM. MOD/DEL REPLY

    ManyToMany로 세개의 테이블이 연결될때 두개까지는 문제가 없는데, 세개부터는 Table생성이 되지 않고 있습니다. 어떤 문제 일까요?

    @ManyToMany()
    @JoinTable(
    name="ROLE_AUTHORITY",
    joinColumns=@JoinColumn(name="ROLE_ID",
    inverseJoinColumns=@JoinColumn(name="AUTHORITY_ID"
    )
    private Set<Authority> authorities = new HashSet<Authority>();

    @ManyToMany()
    @JoinTable(name = "GROUP_ROLE",
    joinColumns = @JoinColumn(name = "GROUP_ID",
    inverseJoinColumns = @JoinColumn(name = "ROLE_ID";)
    private Set<Role> roles = new HashSet<Role>();

    Authority, Role, ROLE_AUTHORITY, GROUP_ROLE까지는 테이블 생성을 확인했는데, Group 테이블은 생성이 되어 있지 않고, Group 객체를 만들어서 save하려고 하자 에러가 발생합니다.

    ManyToMany에 대해 세개까지 연결되는게 문제가 있는지요?

    seeyoungAtgmail.com으로 알려주시면 대단히 감사하겠습니다.

    Favicon of https://whiteship.tistory.com BlogIcon 기선 2008.03.18 22:33 신고 PERM MOD/DEL

    Group ---> Role ---> Authority
    세 개의 Entity들이 이렇게 연관되어 있나보군요.
    재미있겠네요. 해보고 블로그에 올리겠습니다.

    저런 상황에서 JoinTable을 사용하여 맵핑하고, Group을 저장하면 되는거군요.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.18 23:11 PERM MOD/DEL

    아항.. Group이라는 이름이 사용하시는 DB에 키워드로 잡혀있어서 테이블을 못 만든것 같습니다.

    Group위에 붙인 @Entity에 name 속성에 다른 이름을 주고 해보시면 잘 될 겁니다. :)

  2. seeyoung 2008.03.19 11:23 PERM. MOD/DEL REPLY

    Thanks SO MUCH!!!

    맞습니다. DB Keyword인듯 합니다. Class명을 아여 UserGroup으로 변경했는데, 잘 되는군요.

    정말 감사합니다.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.19 12:28 PERM MOD/DEL

    :)

  3. seeyoung 2008.03.19 11:36 PERM. MOD/DEL REPLY

    아하... group by에 group 키워드에 걸린거 같군요.

    Favicon of http://whiteship.tistory.com BlogIcon 기선 2008.03.19 12:27 PERM MOD/DEL

    오호~ 그렇군요.ㅋㅋ

Write a comment.


OneToMany 관계 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:18


Bag으로 맵핑하기

  • Bag은 1대다 양방향 관계를 맵핑하는 가장 효율적인 콜렉션이다. 왜? index도 없고, 중복도 허용하기 때문에, 그냥 넣기만 하면 된다.
  • 그러나 2개의 bag 타입 콜렉션을 동시에 eaget-fetching 할 수 없다.(13장에서 다룬다.)
  • Bag 타입을 사용하려면 변수의 타입은 Collection, 구현체는 ArrayList를 사용한다.
  • 중복 데이터를 넣는 일은 코딩을 잘 해서 방지하면 되지만, 어차피 mappedby 속성(inverse)를 Collection에 걸어두었기 때문에, 여러번 추가해도 SQL은 실행되지 않는다.(그래도.. 메모리 상에서는 콜렉션에 들어갈텐데, 이러면, 안 되지 않을까.. 흠)
Item.java
@OneToMany(mappedBy = "item")
private Collection<Bid> bids = new ArrayList<Bid>();
Bidjava
@ManyToOne
private Item item;

List로 맵핑하기

  • 콜렉션에 있는 Entity들의 index가 필요하다면, List로 맵핑하면 된다.
  • 콜렉션을 맵핑할 때, 콜렉션의 엔티티는 index를 가지고 있을 추가적인 컬럼을 설정한다.(@IndexColumn)
  • 양방향 설정할 때, 콜렉션에 index가 있는 경우(List, Map, 배열)에는 One쪽을 무시(inverse)한다.
  • 왜? 콜렉션이 DB를 수정할 때 필요로 하는 정보(index)를 가지고 있기 때문에, 콜렉션을 기준으로 DB를 수정해야 한다. -> Bid테이블에 Bid_Index 컬럼이 있는데, 이 안에 넣을 값은 Item에 있는 List<Bid> 콜렉션에 넣어야 알 수 있자나. 그러니까 Bid를 기준으로 SQL을 만들 수 없지. 콜렉션에 넣을 때(item.getBids().add(bid)) Bid_Index에 넣을 값을 알 수 있으니까, 콜렉션을 기준으로 SQL을 생성하고, 그 반대쪽 SQL은 발생하지 않도록 inverse 해줘야 해.
  • 따라서 Item과 Bid의 관계에서 Bid쪽의 변화를 무시해야 하는데, @ManyToOne에는 mappedBy 속성이 없다. 그래서 꼼수가 필요하다.
Item.java
@OneToMany
@JoinColumn(name="ITEM_ID", nullable = false)
@IndexColumn(name = "BID_POSITION")
private List<Bid> bids = new ArrayList<Bid>();
Bid.java
@ManyToOne
@JoinColumn(name="ITEM_ID", updatable=false, insertable=false)
private Item item;
  • item.getBids().add(bid); 이렇게 할 때 SQL(update Bid set ITEM_ID=?, BID_POSITION=? where id=?)을 날린다.
  • Bid 와 Item에 설정한 JoinColumn은 같은 이름이어야 한다. 둘 다 Many쪽(여기서는 Bid)에 있는 외례키 컬럼을 나타낸다.
  • JPA는 indexed list를 지원하지 않기 때문에, 하이버네이트의 @IndexColumn 애노테이션을 사용한다.

Join Table로 맵핑하기

  • OneToMany관계가 optional인 경우에 사용하면 좋다. 그러지 않고, 외례키 컬럼이 Null을 허용하도록 해도 되긴 되지만 비추한다.
  • OneToOne관계를 Join Table로 맵핑한 것과 비슷하지만, 그 때는 두 개의 키를 모두 unique로 설정했지만, 이번에는 Many쪽의 주키만 unique로 설정한다. 왜? Join Table에서 One쪽의 주키값은 여러 번 등장할 수 있고, Many쪽의 주키값은 한 번만 등장할 수 있다. 그래야 OneToMany 관계가 되니까.
Item.java
@ManyToOne
@JoinTable(
name="ITEM_USER",
joinColumns=@JoinColumn(name="ITEM_ID"),
inverseJoinColumns=@JoinColumn(name="USER_ID")
)
private User user;
  • 양방향으로 설정할 때는 User에 Item의 콜렉션을 추가한다.
  • ITEM_USER라는 연관 테이블을 만들고, 그 테이블에 ITEM_ID라는 컬럼과 USER_ID 컬럼을 만든다.
  • ITEM_USER 테이블과 Item 테이블과의 연관을 맺을 컬럼은 USER_ID 컬럼이다. 즉 이 컬럼이 Item 테이블에 생긴다.(InverseJoinColumns)
@OneToMany(mappedBy = "user")
private Set<Item> items = new HashSet<Item>();
  • 콜렉션의 변화는 무시한다.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

  1. Favicon of https://java.ihoney.pe.kr BlogIcon 허니몬 2013.05.01 10:59 신고 PERM. MOD/DEL REPLY

    아!!

    역시... 기본적인 이론을 바탕을 가지고 시작했어야 하는 하이버네이트 ㅎㅎ
    주먹구구식으로 도전했다가 낭패만 보다가 여기서 작은 깨달음을 얻습니다.
    ^^ 좋은 정보 감사합니다.

Write a comment.


Multy-valued entity 연관

Hibernate/Chapter 7 : 2008.02.09 10:16


  • 1대다 관계
  • 다대다 관계
  • Join Table에 컬럼 추가하기
  • 맵 맵핑하기

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


OneToOne Join Table 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:15


특징

  • 만약 One To One 관계에 있는 두 Entity가 optional 하다면...
  • 이전에 배운 외례키 사용하는 맵핑을 한 다음에, 외례키를 nullable하게 설정하게 할 수 있겠지만...
  • 관계에 추가적인 속성이 추가될 수도 있을테니, 두 Entity의 id를 모두 unique하게 가지고 있고, Join Table을 만들 수도 있다. ORM이 이런 작업을 해줄 수 있다.
  • CaveatEmptor에서는 Shipment와 Item의 관계. Shipment가 Item을 가지고 있을 수도 있고(물건을 배송 했다.), 없을 수도(배송을 안해서 물건 판 사람이 돈을 못 받을 것이다.) 있다. 배송을 했을 경우에만 두 관계를 나타내는 테이블에 레코드를 추가해야 할 것이다.

맵핑하기

Shipment.java
@OneToOne
@JoinTable(name = "ITEM_SHIPMENT", joinColumns = @JoinColumn(name = "SHIPMENT_ID"), inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
private Item auction;
  • Item과 Shipment는 별개의 라이프 사이클을 가지고 있다가, Shipment에 Item을 setting하는 순간, ITEM_SHIPMENT에 레코드가 추가된다.
  • 만약 Join Table에 별도의 속성이 더 필요할 때가 있는데, 그럴 때는 2차 테이블(8장에서 살펴볼 예정)을 사용해서 설정할 수 있다.
@Entity
@Table(name = "SHIPMENT")
@SecondaryTable(name = "SHIPMENT_ITEM")
public class Shipment {

...

@OneToOne
@JoinColumn(table = "SHIPMENT_ITEM", name = "ITEM_ID")
private Item auction;

...
}
  • SecondTable을 정의해 두고, 그 테이블로 옮길 속성을 JoinColumn에서 table 속성을 사용하여 설정한다.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


OneToOne 외례키 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:12


특징

  • 주키를 공유하는 것이 아니라, 외례키로 관계를 맺는다.
  • User---->Address 의 관계에서 User는 Source, Address는 Target.
  • Source에서 Target이 한 개씩 맵핑되기 때문에, ManyToOne으로 Unique 속성을 true로 맵핑한다.(XML에서만)
  • 양방향으로 맵핑할 때는 Address---->User로 맵핑도 해줘야 한다.
  • 하이버네이트에 Address의 user가 반대쪽 관계를 나타내는 속성의 inverse라는 것을 표현해준다.

맵핑하기

  • 단뱡향 맵핑
User.java
@OneToOne
@JoinColumn(name="SHIPPING_ADDRESS_ID")
@Cascade( { CascadeType.SAVE_UPDATE })
private Address shippingAddress;
  • Address의 주키 속성은 그냥 일반적인 id로 설정하고 기본 주키 생성기를 사용한다. 커스텀 주키 생성기 사용할 필요 없슴.
  • 양방향 맵핑
Address.java
@OneToOne(mappedBy = "shippingAddress")
private User user;
  • mappedBy를 설정해 주어야 Address에 별도의 외례키 컬럼을 만들지 않는다. 왜? inverse라는 걸 알려줬으니까. inverse? 이건 이미 이 반대 방향의 관계에서 이 둘의 관계에 필요한 속성이 있으니까 불필요한 컬럼(또는 추가적인 SQL)을 만들지 않도록 하이버한테 알려주는 거지.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


주키를 공유하는 맵핑

Hibernate/Chapter 7 : 2008.02.09 10:11


특징

  • 두 테이블에서 같은 주키값을 사용한다.
  • 문제는 A-->B 에서 B가 A의 주키값을 사용하도록 하는 것이다. 주기를 공유하도록 설정해야 한다.

맵핑하기

  • JPA는 공유할 주키 생성기를 지원하는 표준화된 방법이 없다.
  • 하이버네이트는 커스텀 id 생성기를 위한 확장 애노테이션을 제공한다.
User.java
@OneToOne
@PrimaryKeyJoinColumn
@Cascade( { CascadeType.SAVE_UPDATE })
private Address shippingAddress;
  • @PrimaryKeyJoinColumn: JOINE을 이용한 상속구조(Table Per Subclass)에서 하위 타입에서 상위 타입의 주키를 자신의 주키이자 외례키로 설정할 때 사용. 여기(1:1맵핑)서는 참조하는 쪽(User)의 주키를 참조되는 쪽(Address)의 외례키로 사용하겠다는 설정이다.
Address.java
@Id
@GeneratedValue(generator = "addressIdGenerator")
@GenericGenerator(name = "addressIdGenerator", strategy = "foreign", parameters = @Parameter(name = "property", value = "user"))
@Column(name = "ADDRESS_ID")
private Long id;

@OneToOne
private User user;
  • JPA에서는 커스텀 식별자 생성기를 만들 수 없기 때문에, 하이버네이트 애노테이션을 이용해야 한다.
테스트 코드
@Test
public void userAndAddress() throws Exception {
Session session = sessionFactory.openSession();
User user = new User();

Address address = new Address();
// Address의 user 속성은 null이면 안 된다. id를 세팅해 주려고 할 때 identifierGeneration 에러 난다.
address.setUser(user);

user.setShippingAddress(address);
session.save(user);

session.flush();
session.close();
assertNotNull(user.getId());
assertNotNull(user.getShippingAddress().getId());
}
  • Fake Object인가... 저렇게 하지 않으면 에러 발생.

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.


Single-valued entity 연관

Hibernate/Chapter 7 : 2008.02.09 10:10


  • User에 Component로 맵핑했던 Address를 별도의 테이블로 구분하고 싶을 수 있다. 다른 Entity에서 Address를 공유할 수 있다. 이럴 때 1:1 맵핑이 된다.
  • 주키를 공유하는 맵핑
  • OneToOne 외례키 맵핑
  • Join Table 맵핑

'Hibernate > Chapter 7' 카테고리의 다른 글

다형적인 ManyToOne 연관  (0) 2008.02.20
하이버네이트 애노테이션과 XML 설정 사이의 Fetching 기본값 차이  (0) 2008.02.20
맵 맵핑하기  (0) 2008.02.09
Join Table에 컬럼 추가하기  (0) 2008.02.09
ManyToMany 관계 맵핑  (7) 2008.02.09
OneToMany 관계 맵핑  (1) 2008.02.09
Multy-valued entity 연관  (0) 2008.02.09
OneToOne Join Table 맵핑  (0) 2008.02.09
OneToOne 외례키 맵핑  (0) 2008.02.09
주키를 공유하는 맵핑  (0) 2008.02.09
Single-valued entity 연관  (0) 2008.02.09
top

Write a comment.