[Hibernate] 8. Hibernate(하이버네이트) 기타 등등
- 식별자 클래스(IDClass)와 맵핑클래스(Entity)클래스의 멤버변수명이 같아야 한다.
- Serializable 인터페이스 구현해야함
- equals(), hashCode()를 구현해야함
- 기본 생성자( Default Constructor)를 구현해야함.
기타
Composite ID , Composite Primary Key
복합키로 여러개의 컬럼이 하나의 Key가 되는 형태이다.
아래는 아파트 테이블로 동/호수, 소유자 이름 컬럼을 갖는다. 동/호수가 Primary Key이다.
APT_DONG 과 APT_HO 가 복합키를 구성한다.
CREATE TABLE `DB_TEST`.`Apartment` ( `APT_DONG` INT NOT NULL COMMENT '', `APT_HO` INT NOT NULL COMMENT '', `APT_OWNER_NAME` VARCHAR(45) NULL COMMENT '', PRIMARY KEY (`APT_DONG`, `APT_HO`) COMMENT '') ENGINE = InnoDB DEFAULT CHARACTER SET = utf8; |
@IdClass 방식
맵핑 클래스
package org.onecellboy.db.hibernate.table;
import java.io.Serializable;
import org.onecellboy.db.hibernate.table.Apartment_Embeddable.APT_PK;
public class APT_PK_Idclass implements Serializable{ private static final long serialVersionUID = 1L;
private int dong; private int ho;
public APT_PK_Idclass() { // TODO Auto-generated constructor stub } public APT_PK_Idclass(int dong, int ho) { this.dong = dong; this.ho = ho; } public int getDong() { return dong; } public void setDong(int dong) { this.dong = dong; } public int getHo() { return ho; } public void setHo(int ho) { this.ho = ho; }
@Override public boolean equals(Object o) { return ((o instanceof APT_PK) && dong == ((APT_PK)o).getDong() && ho == ((APT_PK) o).getHo()); } @Override public int hashCode() { return (int)(dong ^ ho); }
} |
package org.onecellboy.db.hibernate.table;
import java.io.Serializable;
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table;
@Entity @Table(name = "Apartment") @IdClass(APT_PK_Idclass.class) public class Apartment_Idclass {
@Id @Column(name="APT_DONG",columnDefinition="INT") private int dong;
@Id @Column(name="APT_HO",columnDefinition="INT") private int ho;
@Column(name="APT_OWNER_NAME",columnDefinition="VARCHAR(45)") private String owner_name;
public int getDong() { return dong; }
public void setDong(int dong) { this.dong = dong; }
public int getHo() { return ho; }
public void setHo(int ho) { this.ho = ho; }
public String getOwner_name() { return owner_name; }
public void setOwner_name(String owner_name) { this.owner_name = owner_name; }
} |
위에서 보면 복합키를 정의한 APT_PK_Idclass의 멤버변수 dong, ho 가 Entity 클래스 Apartment_Idclass 의 복합키 컬럼과 맵핑되는 멤버변수 dong, ho와 이름이 같다. 복합키를 @IdClass 로 맵핑하는 방식은 복합키 클래스와 Entity 클래스의 멤버변수명이 같아야 한다.
테스트 코드
package org.onecellboy.db.hibernate;
import static org.junit.Assert.*;
import java.io.File;
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.onecellboy.db.hibernate.table.APT_PK_Idclass; import org.onecellboy.db.hibernate.table.Apartment_Embeddable; import org.onecellboy.db.hibernate.table.Apartment_Idclass;
public class CompositeKeyIdclass { static SessionFactory sessionFactory = null; static StandardServiceRegistry registry = null;
@BeforeClass public static void setUp() { registry = new StandardServiceRegistryBuilder().configure(new File("./conf/hibernate/hibernate.cfg.xml")) .build(); sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
}
@AfterClass public static void setDown() { sessionFactory.close(); }
@After public void testAfter() { System.out.println(); System.out.println(); }
@Test public void test() { Session session =null; Transaction tx = null;
System.out.println("======Insert TEST======"); session = sessionFactory.openSession(); tx = session.beginTransaction();
Apartment_Idclass apartment1 = new Apartment_Idclass(); apartment1.setOwner_name("땅부자");
// primary key 설정 apartment1.setDong(104); apartment1.setHo(1303);
session.save(apartment1);
tx.commit(); session.close();
System.out.println(); System.out.println(); System.out.println("======Select TEST======"); session = sessionFactory.openSession(); tx = session.beginTransaction();
APT_PK_Idclass apt_pk=new APT_PK_Idclass(apartment1.getDong(),apartment1.getHo());
Apartment_Idclass apartment2 =session.get(Apartment_Idclass.class, apt_pk);
System.out.println("*** Dong = "+apartment2.getDong()); System.out.println("*** Ho = "+apartment2.getHo()); System.out.println("*** Owner = "+apartment2.getOwner_name());
tx.commit(); session.close(); }
} |
결과
======Insert TEST====== Hibernate: insert into Apartment (APT_OWNER_NAME, APT_DONG, APT_HO) values (?, ?, ?)
======Select TEST====== Hibernate: select apartment_0_.APT_DONG as APT_DONG1_0_0_, apartment_0_.APT_HO as APT_HO2_0_0_, apartment_0_.APT_OWNER_NAME as APT_OWNE3_0_0_ from Apartment apartment_0_ where apartment_0_.APT_DONG=? and apartment_0_.APT_HO=? *** Dong = 104 *** Ho = 1303 *** Owner = 땅부자 |
@Embeddable 방식
idClass 방식과 비슷하나 멤버변수명이 동일하지 않아도 된다. @AttributeOverrides 를 통해 멤버변수와 컬럼을 맵핑할 수 있다.
따라서 식별자 클래스를 여러 다른 Entity 에서 사용가능하다. 생각해보면 복합키는 하나의 테이블 뿐 아니라 foreign key등으로 여러 테이블에서 쓰인다.
Embeddable은 복합키, 복합 외래키에서 쓰이기 좋다.
맵핑 클래스
package org.onecellboy.db.hibernate.table;
import java.io.Serializable;
import javax.persistence.AttributeOverrides; import javax.persistence.AttributeOverride; import javax.persistence.Column; import javax.persistence.Embeddable; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table;
@Entity @Table(name = "Apartment") public class Apartment_Embeddable { @Embeddable public static class APT_PK implements Serializable{ private static final long serialVersionUID = 1L;
protected int dong; protected int ho;
public APT_PK() { // TODO Auto-generated constructor stub } public APT_PK(int dong, int ho) { this.dong = dong; this.ho = ho; } public int getDong() { return dong; } public void setDong(int dong) { this.dong = dong; } public int getHo() { return ho; } public void setHo(int ho) { this.ho = ho; }
@Override public boolean equals(Object o) { return ((o instanceof APT_PK) && dong == ((APT_PK)o).getDong() && ho == ((APT_PK) o).getHo()); } @Override public int hashCode() { return (int)(dong ^ ho); }
}
@EmbeddedId @AttributeOverrides(value = { @AttributeOverride(name="dong", column=@Column(name="APT_DONG",columnDefinition="INT")), @AttributeOverride(name="ho", column=@Column(name="APT_HO",columnDefinition="INT")) }) private APT_PK id;
@Column(name="APT_OWNER_NAME",columnDefinition="VARCHAR(45)") private String owner_name;
public APT_PK getId() { return id; }
public void setId(APT_PK id) { this.id = id; }
public String getOwner_name() { return owner_name; }
public void setOwner_name(String owner_name) { this.owner_name = owner_name; }
} |
위의 예제에서는 Embeddable 클래스를 Inner 클래스로 선언했다. 이 Embeddable 클래스가 범용적으로 다른 곳에서도 사용된다면 외부로 빼주면 된다.
설명
코드 |
@Embeddable public static class APT_PK implements Serializable{ private static final long serialVersionUID = 1L;
protected int dong; protected int ho; |
설명 |
복합키는 Serializable, 직렬화 가능 클래스이다. |
코드 |
@Override public boolean equals(Object o) { return ((o instanceof APT_PK) && dong == ((APT_PK)o).getDong() && ho == ((APT_PK) o).getHo()); } @Override public int hashCode() { return (int)(dong ^ ho); } |
설명 |
equals()와 hashCode() 메소드는 복합키 비교에서 사용되므로 꼭 Override해주자. Composite-ID 의 경우 equals(), hashCode() 를 override 하지 않는다면 아래와 같은 경고가 발생한다. 1601 [WARN] RootClass: HHH000038: Composite-id class does not override equals(): .... 1602 [WARN] RootClass: HHH000039: Composite-id class does not override hashCode(): ..... |
코드 |
@EmbeddedId @AttributeOverrides(value = { @AttributeOverride(name="dong", column=@Column(name="APT_DONG",columnDefinition="INT")), @AttributeOverride(name="ho", column=@Column(name="APT_HO",columnDefinition="INT")) }) private APT_PK id; |
설명 |
복합키를 설정하는 부분이다. |
테스트 코드
package org.onecellboy.db.hibernate;
import static org.junit.Assert.*;
import java.io.File;
import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.onecellboy.db.hibernate.table.Apartment_Embeddable; import org.onecellboy.db.hibernate.table.Apartment_Embeddable.APT_PK;
public class CompositePrimaryKey { static SessionFactory sessionFactory = null; static StandardServiceRegistry registry = null;
@BeforeClass public static void setUp() { registry = new StandardServiceRegistryBuilder().configure(new File("./conf/hibernate/hibernate.cfg.xml")) .build(); sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
}
@AfterClass public static void setDown() { sessionFactory.close(); }
@After public void testAfter() { System.out.println(); System.out.println(); }
@Test public void test() { Session session =null; Transaction tx = null;
System.out.println("======Insert TEST======"); session = sessionFactory.openSession(); tx = session.beginTransaction();
Apartment_Embeddable apartment1 = new Apartment_Embeddable(); apartment1.setOwner_name("땅부자");
Apartment_Embeddable.APT_PK id=new Apartment_Embeddable.APT_PK(103, 1204); apartment1.setId(id);
session.save(apartment1);
tx.commit(); session.close();
System.out.println(); System.out.println(); System.out.println("======Select TEST======"); session = sessionFactory.openSession(); tx = session.beginTransaction();
Apartment_Embeddable.APT_PK apt_pk=new Apartment_Embeddable.APT_PK(apartment1.getId().getDong(), apartment1.getId().getHo());
Apartment_Embeddable apartment2 =session.get(Apartment_Embeddable.class, apt_pk);
System.out.println("*** Dong = "+apartment2.getId().getDong()); System.out.println("*** Ho = "+apartment2.getId().getHo()); System.out.println("*** Owner = "+apartment2.getOwner_name());
tx.commit(); session.close();
}
} |
결과
======Insert TEST====== Hibernate: insert into Apartment (APT_OWNER_NAME, APT_DONG, APT_HO) values (?, ?, ?)
======Select TEST====== Hibernate: select apartment_0_.APT_DONG as APT_DONG1_0_0_, apartment_0_.APT_HO as APT_HO2_0_0_, apartment_0_.APT_OWNER_NAME as APT_OWNE3_0_0_ from Apartment apartment_0_ where apartment_0_.APT_DONG=? and apartment_0_.APT_HO=? *** Dong = 103 *** Ho = 1204 *** Owner = 땅부자 |
Composite Key Join(복합키 조인)/여러 컬럼 조인
여러개의 컬럼으로 조인하고 싶을 경우
일반적인 한개의 컬럼 조인의 경우 아래와 같다.
@OneToMany(fetch=FetchType.EAGER, targetEntity=Users_Coupon.class, cascade=CascadeType.ALL) @JoinColumn(name = "uc_user_id", referencedColumnName="usr_id") private Set<Users_Coupon> Users_coupons; |
하지만 Composite Key Join 과 같은 여러 컬럼으로 조인하는 경우는 아래와 같다.
@OneToMany(cascade=CascadeType.ALL) @JoinColumns ({ @JoinColumn(name="parentCivility", referencedColumnName = "isMale"), @JoinColumn(name="parentLastName", referencedColumnName = "lastName"), @JoinColumn(name="parentFirstName", referencedColumnName = "firstName") }) public Set<Child> children; |
MapsId ( EmbeddedId primary key, 복합키 관계)
@MapsId는 복합키 관계 설정시 사용된다.
@MapsId 는 외래 키와 매핑한 연관관계를 기본 키에도 매핑하겠다는 뜻이다.
말이 어려우니 예를들어 아래를 보자.
// parent entity has simple primary key
@Entity public class Employee { @Id long empId; String name; ... }
// dependent entity uses EmbeddedId for composite key
@Embeddable public class DependentId { String name; long empid; // corresponds to primary key type of Employee }
@Entity public class Dependent { @EmbeddedId DependentId id; ... @MapsId("empid") // maps the empid attribute of embedded id @ManyToOne(name="EMPLOYEE_ID") Employee emp; } |
@MapsId("empid") 는 복합키 DependentId 클래스의 "empid" 인 멤버변수명이다.
@MapsId는 Composite Primary Key(Embeddable) 의 일부만 관계에서 사용될 해당 컬럼명만 명시하면 된다. 명시하지 않으면 전체가 사용된다.
Dependent 테이블의 "EMPLOYEE_ID" 컬럼은 Employee 테이블의 기본키인 "empId" 를 참조한다. 이때 Dependent 의 "EMPLOYEE_ID" 컬럼은 복합키의 요소이고 이 컬럼이 DependentId 클래스의 empid 변수와 맵핑됨을 알리는 것이다.
@AttributeOverrides 없이도 복합키 클래스의 변수와 컬럼을 맵핑 시킬 수 있는 방식이다.
필자는 보통 Embedded 에 대해 아래와 같이 명시적으로 맵핑한다. :
@Entity public class Dependent { @EmbeddedId @EmbeddedId @AttributeOverrides(value = { @AttributeOverride(name="name", column=@Column(name="NAME",columnDefinition="VARCHAR")), @AttributeOverride(name="empid", column=@Column(name="EMPLOYEE_ID",columnDefinition="BITINT")) }) DependentId id; |
NamedNativeQuery or Subselect ( = logical view )
MappedSuperclass
참고 : https://www.concretepage.com/hibernate/example-mappedsuperclass-hibernate
https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/MappedSuperclass.html
공통적인 부모 맵핑 클래스를 만들고 이를 상속받아 확장하는 방법
예를 들어 테이블이 lion, tiger 가 있다.
Fetching Strategies ( 패칭 전략)
참고 :
https://www.mkyong.com/hibernate/hibernate-fetching-strategies-examples/
http://www.javamakeuse.com/2015/03/tutorial-hibernate-4-subselect-fetching.html
관계된 데이터(OneToMany 등등) 들을 가져올때 Join을 사용할 것인지, subquery를 사용할 것인지등 선택하는 것이다. 튜닝관련 옵션으로 상황에 따라 판단해서 결정하고 사용해야 한다.
Paging(페이징, 원하는 지점에서 개수만큼 가져오기)
데이터베이스마다 다르겠지만 Mysql의 경우 limit 키워드를 이용하여 원하는 개수의 결과를 가져오는 방법이다.
예제
query = session.createQuery("SELECT sdt.id, sdt.name FROM Student sdt"); query.setFirstResult(1); query.setMaxResults(3); |
setFirstResult() : 시작 지점이다. index로 0부터 시작한다.
setMaxResults() : 결과로 가져올 개수이다.
위의 경우는 1부터 3개 가져오라는 것이다.
결과 쿼리
select student0_.ID as col_0_0_, student0_.NAME as col_1_0_ from Student student0_ limit ?, ? |
Column Lazy Loading ( 컬럼 게으른(늦은) 불러오기 )
https://vladmihalcea.com/the-best-way-to-lazy-load-entity-attributes-using-jpa-and-hibernate/
@OneToMany ..등 관계 맵핑시 Lazy Loading으로 부하를 줄이는 방법이 존재한다는 것을 알고 있다.
Entity 맵핑시 하나의 컬럼에도 Lazy Loading을 적용할 수 있다.
컬럼에 Lazy Loading을 적용해야하는 경우를 생각해보자.
예를 들어 게시판을 만들었다고 하자. 제목, 글쓴이, 날짜, 내용의 컬럼이 존재할 때 게시판 목록 구성을 위해서는 내용은 일단 필요없다. 내용은 또한 데이터량이 많을 것이다. 이때는 내용을 Lazy Loading 처리하고 하나의 게시글을 볼 때 요청하면 부하를 확실히 줄일 수 있다.
그렇다면 컬럼에 대한 Lazy Loading 은 어찌 처리할까. OneToOne 관계를 이용하면 된다. 간략한 정보에 대한 맵핑클래스와 자세한 정보가 들어있는 맵핑클래스를 OneToOne 관계에 두고 Lazy Loading 하면 된다.
UniqueContraint(유일성 제한)
해당 annotation 맵핑 클래스를 통해 DB 스키마 생성을 위해 쓰인다.
하나의 컬럼에 대한 Unique는 보통 컬럼에 unique=true를 통해 설정한다.
@Column(name="PHONE_NUMBER",columnDefinition="VARCHAR(45)",unique = true) private String number; |
만약 다수의 컬럼에 대한 Unique의 경우는 @Table에서 설정한다.
@Table( name = "people", uniqueConstraints = {@UniqueConstraint(columnNames = {"name", "number"})} ) |
이 경우 Constraint 명이 랜덤으로 자동으로 생성된다. 예) uk_3rkljasdkljefkljdf
만약 Constraint 에 대해 이름을 부여하고 싶다면 아래와 같이 하면 된다.
@Table( name = "people", uniqueConstraints = { @UniqueConstraint( columnNames = {"name", "number"}, name="uk_people_name_number")} ) |
여러개의 Constraint 는 아래와 같다.
@Table( name = "people", uniqueConstraints = { @UniqueConstraint(columnNames = {"name", "number"}),name="uk_people_name_number"}, @UniqueConstraint(columnNames = "login_id")},
) |
컬럼 하나에 대한 Unique 설정도 가능하다. 예: login_id
@Formula(공식), 컬럼 연산(계산)하기,Subquery 넣기
@Formula 는 sql function 을 이용하여 원하는 하나의 column 을 수식을 이용하여 select 할 수 있다. select에서만 사용된다.
주의할 것은 하나의 column만 반환 될 수 있어야 한다. 즉 select 에서 반화되는 하나의 컬럼에 대한 수식이다.
sql function을 써야하는 경우 아주 유용하다.
수식 예:
@Entity @Table(name="people") public class People {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name="PEOPLE_ID",columnDefinition="INT") private int id;
@Column(name="PEOPLE_NAME",columnDefinition="VARCHAR(45)") private String name;
@Formula( "upper( PEOPLE_NAME )" ) private String upperName;
public String getUpperName() { return upperName; } (생략....) } |
@Formula 내에는 컬럼에 대한 수식을 넣으면 된다.(컬럼명이다. 멤버변수명이 아니다.)
테스트
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction();
People people = session.get(People.class, select_id); System.out.println("*people.getUpperName : "+people.getUpperName());
tx.commit(); session.close(); |
아래의 결과 이다.
Hibernate: select people_bi0_.PEOPLE_ID as
PEOPLE_I1_4_0_, people_bi0_.PEOPLE_NAME as PEOPLE_N2_4_0_, upper(
people_bi0_.PEOPLE_NAME ) as formula0_0_ from people
people_bi0_ where people_bi0_.PEOPLE_ID=? |
select 에 수식이 입력된 것을 볼 수 있다.
서브쿼리를 이용 시 아래와 같다.:
@Formula( "( select Max(o.PEOPLE_ID) from people o)" ) private int maxID;
public int getMaxID() { return maxID; } |
이런식으로 서브쿼리시에는 반드시 테이블에 alias를 주어야한다. 또한 해당 서브쿼리의 반환은 1개여야 한다.
결과
Hibernate: select people_bi0_.PEOPLE_ID as PEOPLE_I1_4_0_, people_bi0_.PEOPLE_NAME as PEOPLE_N2_4_0_, ( select Max(o.PEOPLE_ID) from people o) as formula0_0_ from people people_bi0_ where people_bi0_.PEOPLE_ID=? |
Lazy Loading 시 주의점
Lazy Loading 은 Session이 Close()되기전에만 동작한다. 만약 Session 이 Close()된 후 Lazy Loading인 데이터에 접근을 한다면 아래와 같은 에러가 발생한다.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: org.onecellboy.db.hibernate.table.People_Uni.clubs, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:582) at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:201) at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:145) at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:261) at org.onecellboy.db.hibernate.OneToManyJoinTableUni.testPeople(OneToManyJoinTableUni.java:147) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
|