Hibernate Core VS JPA

















Hibernate JPA
네 가지 상속 맵핑 전략을 제공하고, 상속 전략을 혼용하는 것이 가능하다. 네 가지 맵핑 전략을 표준화 했다. 이 중에선 Table Per Class Hierarchy와 Table Per Subclass만 JPA 호환 필수요소다.
영속성 대상이 되는 클래스의 상위 클래스는 abstract class나 interface도 될 수 있다. abstract class만 이식 가능하다.
Value Type을 위한 유연한 내장 타입과 컴버터를 제공한다. 표준화된 방법으로 맵핑 타입을 자동으로 찾아 준다. 커스텀 타입을 찾을 때는 하이버네이트 확장 애노테이션을 사용한다.
막강한 타입 확장 시스템 Enum을 위한 내장 타입 표준이 필요하다.

Enum Type 맵핑하기 – 실습

1. 테스트 코드

[#M_ more.. | less.. |

package chapter5.customType.example;

 

import java.util.List;

 

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.test.context.transaction.TransactionConfiguration;

import org.springframework.transaction.annotation.Transactional;

 

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration

@Transactional

@TransactionConfiguration(defaultRollback=true)

public class MemberTest {

 

       @Autowired

       private SessionFactory sessionFactory;

      

       @Test

       public void save() throws Exception {

             Member member = new Member();

             member.setMemberType(MemberType.ADMIN);

             member.setName(기선);

            

             Session session = sessionFactory.openSession();

             session.save(member);

 

             List<Member> memberList = session.createQuery(“from Member m where m.memberType = chapter5.customType.example.MemberType.ADMIN”).list();

             Member member2 = memberList.get(0);

            

             System.out.println(member);

             assertEquals(MemberType.ADMIN, member2.getMemberType());

            

             session.flush();

             session.close();

       }

}

 

_M#]
* 그냥 하나를 저장하고 그걸 다시 꺼내 봅니다. 꺼낼 때 get을 사용해도 되지만, 쿼리를 어떻게 작성해야 하는지 보기 위해서 HQL로 작성했습니다.

* 그냥 문자열일 뿐인데, 저걸 읽어서 Enum 타입을 알아내고 그것의 name()을 호출한 값으로 대체 해주는 하이버… 정말 똑똑하지 않나요. 대단합니다.

* 위 클래스에 있는 애노테이션들을 붙이면 Spring 2.5 전에 사용하던 AbstractTransactionalDataSource어쩌구저쩌구를 상속받은 클래스와 같은 녀석이 됩니다.(엄밀히 따지면 같지는 않습니다. applicationContext를 가지고 있지 않아서, 명시적으로 getBean() 할 수가 없습니다.)

2. Member 클래스와 MemberType 클래스
[#M_ more.. | less.. |

package chapter5.customType.example;

 

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.EnumType;

import javax.persistence.Enumerated;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

 

import org.hibernate.annotations.Parameter;

import org.hibernate.annotations.Type;

import org.hibernate.annotations.TypeDef;

 

//@TypeDef(name=”memberType”, typeClass=chapter5.customType.example.StringEnumMemberType.class, parameters={@Parameter(name = “enumClassname”, value = “chapter5.customType.example.MemberType”)})

@Entity

public class Member {

 

       @Id

       @GeneratedValue(strategy=GenerationType.AUTO)

       private Long id;

 

       @Enumerated(EnumType.STRING)

       @Column(nullable=false, name=”TYPE”)

//     @Type(type=”memberType”)

       private MemberType memberType;

 

       private String name;

 

       public Long getId() {

             return id;

       }

 

       public void setId(Long id) {

             this.id = id;

       }

 

       public MemberType getMemberType() {

             return memberType;

       }

 

       public void setMemberType(MemberType memberType) {

             this.memberType = memberType;

       }

 

       public String getName() {

             return name;

       }

 

       public void setName(String name) {

             this.name = name;

       }

 

       @Override

       public String toString() {

             return “id: ” + id + ” name: ” + name + ” type: ” + memberType;

       }

}

package chapter5.customType.example;

 

public enum MemberType {

      

       ADMIN, USER

 

}

 

_M#]
* 별거 없습니다. id, membeType, name.

* MemberType은 매우 단순한 enum입니다.

* 주목해야 할 것은 MemberType 속성 위에 붙인 @Enumerated 애노테이션 입니다. 이녀석이 굉장한 일을해줍니다.

3. XML 설정
[#M_ more.. | less.. |

<?xml version=“1.0” encoding=“UTF-8”?>

<beans xmlns=“http://www.springframework.org/schema/beans”

       xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance”

       xmlns:aop=“http://www.springframework.org/schema/aop”

       xmlns:context=“http://www.springframework.org/schema/context”

       xmlns:jee=“http://www.springframework.org/schema/jee”

       xmlns:lang=“http://www.springframework.org/schema/lang”

       xmlns:p=“http://www.springframework.org/schema/p”

       xmlns:tx=“http://www.springframework.org/schema/tx”

       xmlns:util=“http://www.springframework.org/schema/util”

       xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd

             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

             http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd

             http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.xsd

             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

             http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd”>

 

       <context:component-scan base-package=“chapter4.identity” />

 

       <tx:annotation-driven transaction-manager=“transactionManager” />

 

       <bean id=“sessionFactory”

              class=“org.springframework.orm.hibernate3.annotation.
AnnotationSessionFactoryBean”
>

             <property name=“dataSource” ref=“dataSource” />

             <property name=“hibernateProperties”>

                    <props>

                           <prop key=“hibernate.dialect”>org.hibernate.dialect.HSQLDialect</prop>

                           <prop key=“hibernate.show_sql”>true</prop>

                           <prop key=“hibernate.hbm2ddl.auto”>update</prop>

                           <prop key=“hibernate.connection.autocommit”>false</prop>

                    </props>

             </property>

             <property name=“annotatedClasses” ref=“annotatedClasses” />

       </bean>

 

       <util:list id=“annotatedClasses”>

             <value>chapter5.customType.example.Member</value>

       </util:list>

 

       <bean id=“dataSource”

             class=“org.apache.commons.dbcp.BasicDataSource” destroy-method=“close”>

             <property name=“driverClassName” value=“org.hsqldb.jdbcDriver” />

             <property name=“url” value=“jdbc:hsqldb:mem:test” />

             <property name=“username” value=“sa” />

             <property name=“password” value=“” />

       </bean>

 

       <bean id=“transactionManager”

             class=“org.springframework.orm.hibernate3.HibernateTransactionManager”

             p:dataSource-ref=“dataSource” p:sessionFactory-ref=“sessionFactory” />

 

</beans>

_M#]
* 역시나 뭐 별거 없습니다. 그냥 스프링+하이버 SessionFactory, TransactionManager, DataSource입니다.

4. 결과

Hibernate: insert into Member (id, TYPE, name) values (null, ?, ?)
Hibernate: call identity()
Hibernate: select member0_.id as id0_, member0_.TYPE as TYPE0_, member0_.name as name0_ from Member member0_ where member0_.TYPE=’ADMIN’
id: 1 name: 기선 type: ADMIN

결과 쿼리를 보면 재밌습니다. 저는 그냥 @Enumerated 애노테이션 하나 붙였을 뿐인데, 알아서 ADMIN으로 저장해주고, HQL도 알아서 변경해 줍니다. 엘레강트 한 녀석입니다.

Enum Type 만들기

클래스 구현하기.



  • EnhancedUserType와 ParameterizedType 인터페이스를 구현한다.


private Class<Enum> enumClass;

public void setParameterValues(Properties parameters) {
String enumClassName =
parameters.getProperty(“enumClassname”);
try {
enumClass = ReflectHelper.classForName(enumClassName);
} catch (ClassNotFoundException cnfe) {
throw new
HibernateException(“Enum class not found”, cnfe);
}
}

public Class returnedClass() {
return enumClass;
}

public Object fromXMLString(String xmlValue) {
return Enum.valueOf(enumClass, xmlValue);
}

public String objectToSQLString(Object value) {
return ‘\” + ( (Enum) value ).name() + ‘\”;
}

public String toXMLString(Object value) {
return ( (Enum) value ).name();
}

public Object nullSafeGet(ResultSet rs,
String[] names,
Object owner)
throws SQLException {
String name = rs.getString( names[0] );
return rs.wasNull() ? null : Enum.valueOf(enumClass, name);
}

public void nullSafeSet(PreparedStatement st,
Object value,
int index)
throws SQLException {
if (value == null) {
st.setNull(index, Hibernate.STRING.sqlType());
} else {
st.setString( index, ( (Enum) value ).name() );
}
}



  • setParameterValues()는 클래스 이름 가져와서 Class 변수에 세팅한다.


  • fromXMLString(), objectToSQLString(), toXMLString()는 XML과 Enum의 value를 맵핑한다.


  • nullSafeGet()는 DB에서 value를 읽은 다음 Enum으로 반환한다.


  • nullSafeSet()는 Enum에서 DB로 저장할 value를 뽑아낸다.

맵핑하기



  • JPA의 경우 커스텀 타입 클래스 만들지 않고도 String으로 저장하거나, 선택된 Enum의 value를 저장할 수 있다.


public class Comment {

@Enumerated(EnumType.STRING)
@Column(name = “RATING”, nullable = false, updatable = false)
private Rating rating;

}

쿼리 작성하기



  • 다음과 같이 쿼리를 작성할 수 있다.


Query q =
session.createQuery(
“from Comment c where c.rating = auction.model.Rating.BAD”
);


  • c.rating의 rating은 DB 컬럼의 이름이 아니라 객체의 속성이름이다.


“from Member m where m.memberType = ‘” + MemberType.ADMIN + “‘”
“from Member m where m.memberType = chapter5.customType.example.MemberType.ADMIN”

ParameterizedType 만들기

클래스 구현하기



  • DB에 값을 저장할 때 특정 조건에 따라 다른 값으로 변환하여 저장할 필요가 있다면, 파라미터가 필요하다. 이럴 때 유요하다.


  • ParameterizedType 인터페이스를 구현한다.


private Currency convertTo;

@Override
public void setParameterValues(Properties parameters) {
this.convertTo = Currency.getInstance(
parameters.getProperty(“convertTo”));
}


맵핑하기



  • 같은 Value Type 클래스 타입의 객체들 여럿을 맵핑 할 때는 TypeDef 애노테이션을 사용해서, 전역에서 사용할 설정을 정의해 둘 수 있다.


@org.hibernate.annotations.TypeDefs({
@org.hibernate.annotations.TypeDef(
name=“monetary_amount_usd”,
typeClass = persistence.MonetaryAmountConversionType.class,
parameters = { @Parameter(name=“convertTo”, value=“USD”) }
),
@org.hibernate.annotations.TypeDef(
name=“monetary_amount_eur”,
typeClass = persistence.MonetaryAmountConversionType.class,
parameters = { @Parameter(name=“convertTo”, value=“EUR”) }
)
})


  • 위의 설정은 import문 바로 아래 또는 class정의 부분 위나, 별도의 자바 파일, package-info.java 같은 클래스에 둔다.(2.2.1 참조)


  • 다음과 같이 참조해서 사용할 수 있다.


@org.hibernate.annotations.Type(type = “monetary_amount_eur”)
@org.hibernate.annotations.Columns({
@Column(name = “BID_AMOUNT”),
@Column(name = “BID_AMOUNT_CUR”)
})
private MonetaryAmount bidAmount;

CompositeUserType 만들기

클래스 구현하기



public class MonetaryAmountCompositeUserType implements CompositeUserType {

public String[] getPropertyNames() {
return new String[] { “amount”, “currency” };
}

public Type[] getPropertyTypes() {
return new Type[] { Hibernate.BIG_DECIMAL, Hibernate.CURRENCY };
}

public Object getPropertyValue(Object component, int property)
throws HibernateException {
MonetaryAmount monetaryAmount = (MonetaryAmount) component;
if (property == 0)
return monetaryAmount.getAmount();
else
return monetaryAmount.getCurrency();
}

public Object nullSafeGet(ResultSet resultSet, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
BigDecimal value = resultSet.getBigDecimal(names[0]);
if (resultSet.wasNull())
return null;
Currency currency = Currency.getInstance(resultSet.getString(names[1]));
return new MonetaryAmount(value, currency);
}

public void nullSafeSet(PreparedStatement statement, Object value,
int index, SessionImplementor arg3) throws HibernateException,
SQLException {
if (value == null) {
statement.setNull(index, Hibernate.BIG_DECIMAL.sqlType());
statement.setNull(index + 1, Hibernate.CURRENCY.sqlType());
} else {
MonetaryAmount amount = (MonetaryAmount) value;
String currencyCode = amount.getCurrency().getCurrencyCode();
statement.setBigDecimal(index, amount.getAmount());
statement.setString(index + 1, currencyCode);
}
}

public void setPropertyValue(Object arg0, int arg1, Object arg2)
throws HibernateException {
throw new UnsupportedOperationException(“Immutable MonetaryAmount!”);
}

}



  • CompositeUserType 인터페이스를 구현한다.


  • nullSafeSet()은 ResultSet에 담겨있는 두 개의 값을 Monetary 객체의 속성값으로 변환하면 된다.


  • nullSafeSet()은 객체가 가진 두 개의 값을 DB에 저장하도록 statement를 수정한다.


  • getPropertyNames()를 사용해서 Value Type의 속성들을 알려준다.


  • getPropertyValue()를 사용해서 Value Type의 각각의 속성이 가진 값을 알려준다.


  • setPropertyValue()를 사용해서 Value Type의 속성에 값을 설정한다.

맵핑하기



@org.hibernate.annotations.Type(
type = “persistence.MonetaryAmountUserType”
)
@org.hibernate.annotations.Columns(columns = {
@Column(name=“INITIAL_PRICE”),
@Column(name=“INITIAL_PRICE_CURRENCY”, length = 2)
})
private MonetaryAmount initialPrice;


  • 다음과 같이 쿼리를 작성할 수 있다.


from Item i
where i.initialPrice.amount > 100.0
and i.initialPrice.currency = ‘AUD’