Hibernate でJPAを使ったサンプル。利用する主なプロダクトは以下
ライブラリの入手
Hibernate
https://www.hibernate.org/ から Hibernate Core と Hibernate Annotations を入手。hibernate-distribution-3.3.2.GA の 以下をクラスパスに追加
- hibernate3.jar
- antlr-2.7.6.jar (lib.required内に存在)
- dom4j-1.6.1.jar (lib.required内に存在)
- javassist-3.9.0.GA.jar (lib.required内に存在)
- jta-1.1.jar (lib.required内に存在)
- commons-collections-3.1.jar (lib.required内に存在)
hibernate-annotations-3.4.0.GA の 以下をクラスパスに追加
H2
http://www.h2database.com/html/main_ja.html から All Platforms 向けの h2-2009-08-09.zip を入手。以下をクラスパスに追加
- h2-1.1.117.jar (bin内に存在)
TestNG
http://testng.org/doc/index.html から testng-5.10.zip を入手。以下をクラスパスに追加
- testng-5.10-jdk15.jar
Eclipse側には事前に http://beust.com/eclipse などからプラグインを組み込んでおく
プロジェクトの作成
Eclipseで新規Javaプロジェクトを作成。上記で入手したライブラリにクラスパスを通す。
まずはスモールスタートで、Entityのソース作成(パッケージはetc9.domainとした)
package etc9.domain; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="item") public class Item implements Serializable { @Id private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
ポイントは以下
- @Entity にてこのクラスが Entity であることを指示
- @Table(name="item") にてマッピング対象のテーブル名を指定
- @Id にてキーの指定
hibernate.cfg.xmlの作成
hibernate.cfg.xmlをソースフォルダのルートに作成
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.driver_class">org.h2.Driver</property> <property name="connection.url">jdbc:h2:tcp://localhost:9092/hibernate</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <property name="dialect">org.hibernate.dialect.H2Dialect</property> <property name="hibernate.hbm2ddl.auto">create</property> <property name="hibernate.show_sql">true</property> <mapping package="etc9.domain"/> <mapping class="etc9.domain.Item"/> </session-factory> </hibernate-configuration>
ポイントは以下
実行用のテストケース作成
package etc9.domain; import org.h2.tools.Server; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.cfg.Configuration; import org.testng.annotations.AfterSuite; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeSuite; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; public class ItemTest { private static final String BASE_DIR = "db\\h2"; private static Server tcpServer; private SessionFactory sessionFactory; @BeforeSuite public void beforeSuite() throws Exception { tcpServer = org.h2.tools.Server.createTcpServer( new String[] { "-baseDir", BASE_DIR, "-tcpPort", "9092" }).start(); Configuration config = new AnnotationConfiguration().configure(); sessionFactory = config.buildSessionFactory(); } @AfterSuite public void afterSuite() { tcpServer.shutdown(); } @BeforeMethod public void beforeMethod() { Item item = new Item(); item.setId(1); item.setName("Hibernate"); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); session.save(item); transaction.commit(); session.close(); } @Test public void testItem() { Session session = sessionFactory.openSession(); Item item = (Item) session.get(Item.class, 1); assertEquals(item.getName(), "Hibernate"); } }
ポイントは以下
実行
ItemTest をTestNGにて実行すると、BeforeSuite メソッド内にて H2 のプロセスを起動。その後、SessionFactory のインスタンスを作成
このタイミングで、hibernate.hbm2ddl.auto の create 設定により、Entity として登録してある Item に対応するテーブルを Hibernate が自動作成する。hibernate.hbm2ddl.auto に指定できる値は以下
- validate 存在するスキーマとの検証のみ実施
- create SessionFactoryのインスタンス作成時にdropして再作成
- create-drop createの動作に加え、SessionFactory.close時にdrop
- update スキーマの変更時のみ再作成
beforeMethod()にて作成されたItemテーブルにレコードを追加。testItem()ではItemテーブルからselecctしている。
DDLやDMLを一切書かず、Entityクラスの修正も即座に反映され、またデータベースの起動も不要になり、サクサク開発が実施できる。
以降では、この環境を元に機能を追加していく。
IDの自動採番
Item の id は外部より設定する必要があった。これを自動採番するようにする。
Item の id フィールドにGeneratedValueを指定する
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id;
これで、テストケースで指定していた setId(1) が削除でき、IDは自動採番される
@BeforeMethod public void beforeMethod() { Item item = new Item(); // item.setId(1); item.setName("Hibernate");
1対1の関連
上記に続いて注文明細(OrderDetail)と商品(Item)の1対1の関連を作る。
Itemに価格のフィールドを追加。また、商品名を非nullと宣言。
package etc9.domain; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="item") public class Item implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) private String name; private Integer price; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } }
続いて注文明細
package etc9.domain; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Table; @Entity @Table(name="order_detail") public class OrderDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne private Item item; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Item getItem() { return item; } public void setItem(Item item) { this.item = item; } }
@OneToOneアノテーションにて商品と1対1の関連を定義
hibernate.cfg.xmlにはOrderDetailのマッピング指定を追加
<mapping class="etc9.domain.Item"/> <mapping class="etc9.domain.OrderDetail"/>
これで、注文明細と商品の1対1の関連が定義できた。さっそくテストケースを修正。beforeMethod()を以下のようにする。
@BeforeMethod public void beforeMethod() throws Exception { Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); Item item = new Item(); item.setName("Hibernate"); item.setPrice(1000); session.save(item); OrderDetail ditail = new OrderDetail(); detail.setItem(item); session.save(detail); transaction.commit(); session.close(); }
テストメソッドは以下。
@Test public void testOrderDitailItem() { Session session = sessionFactory.openSession(); OrderDetail ditail = (OrderDetail)session.get(OrderDetail.class, 1L); assertEquals(detail.getItem().getName(), "Hibernate"); assertEquals(detail.getItem().getPrice(), new Integer(1000)); }
これで、OrderDetailを取得して、そこからItemを取得できる。Hibernate は OrderDetailの取得時に reft outer join のSQLを発行してItemのレコードも同時に取得している。
1対多の関連
先ほど注文明細を作成し、商品との1対1の関係を作成したので、次は注文(Order)と注文明細(OrderDetail)の1対多の関連を作る。(同値性の考慮はここではしない)
Orderは以下。
@Entity @Table(name="order_t") public class Order implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private Date orderDate; @OneToMany(mappedBy="order") private Collection<OrderDetail> details = new HashSet<OrderDetail>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Date getOrderDate() { return orderDate; } public void setOrderDate(Date orderDate) { this.orderDate = orderDate; } public Collection <OrderDetail> getDetails() { return details; } public void setDetails(Collection <OrderDetail> details) { this.details = details; } }
orderというテーブルはH2で作成できないので、@Table(name="order_t")としてある。@OneToMany(mappedBy="id")
続いて、子テーブルとなるOrderDetail
@Entity @Table(name="order_detail") public class OrderDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne private Item item; @ManyToOne private Order order; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Item getItem() { return item; } public void setItem(Item item) { this.item = item; } public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } }
これで、order_detailテーブルに、order_idカラムが追加され、order_tテーブルの子となる。
hibernate.cfg.xmlにはOrderのマッピング指定を追加
<mapping class="etc9.domain.Item"/> <mapping class="etc9.domain.OrderDetail"/> <mapping class="etc9.domain.Order"/>
Order側のmappedByを以下のようにすると、「ORDER_DETAIL FOREIGN KEY(ID) REFERENCES ORDER_T(ID)」のような参照制約が付与される。
@OneToMany(mappedBy="id") private Collection<OrderDetail> details = new HashSet<OrderDetail>();
また、mappedByを以下のように指定しなかった場合は、ORDER_T_ORDER_DETAIL というORDER_TテーブルとORDER_DETAILテーブルの関連テーブルが作成される。
@OneToMany private Collection<OrderDetail> details = new HashSet<OrderDetail>();
テストメソッドは省略。