Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

39
Hibernate Spring Hibernate Object/Relation Mapping, ORM Java Spring Hibernate Spring Hibernate Hibernate Spring Spring 6

Transcript of Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Page 1: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Hibernate與 Spring

Hibernate 是個物件關係映射(Object/Relation Mapping, ORM)的解決方案,提供 Java 物件模型與關聯式資料庫關聯模型的自動映射,並也提供有持久層所需的快取、鎖定、交易管理等功能。 您可以在 Spring 中整合 Hibernate,Spring 為 Hibernate 進行了封裝,讓您在 Hibernate 的設定與使用上,都可以依循 Spring 一致的使用模型,在使用上可以更為簡化,並可以結合 Spring 的功能,如宣告式的交易管理等功能。

6

Page 2: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�2

6.1 Hibernate入門 這個小節是針對未接觸過 Hibernate 的讀者所安排的,目的在快速了解如何撰寫第一個Hibernate程式,以利接下來的 6.2節中,實際練習 Spring與 Hibernate的整合範例,如果您已經了解 Hibernate的撰寫方式,可以直接略過這個小節。

6.1.1 簡介 Hibernate 在第 5章中 5.1.2的 DataSourceDemo專案中,曾經設計過一個 User-

DAO類別,其中在 find() 方法中,您是這麼設計的:

...

public User find(Integer id) {

Connection conn = null;

PreparedStatement stmt = null;

try {

conn = dataSource.getConnection();

stmt = conn.prepareStatement(

"SELECT * FROM user WHERE id=?");

stmt.setInt(1, id.intValue());

ResultSet result = stmt.executeQuery();

if(result.next()) {

Integer i = new Integer(result.getInt(1));

String name = result.getString(2);

Integer age = new Integer(result.getInt(3));

User user = new User();

user.setId(i);

user.setName(name);

user.setAge(age);

return user;

}

Page 3: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�3

} catch (SQLException e) {

e.printStackTrace();

}

finally {

if(stmt != null) {

try {

stmt.close();

}

catch(SQLException e) {

e.printStackTrace();

}

}

if(conn != null) {

try {

conn.close();

}

catch(SQLException e) {

e.printStackTrace();

}

}

}

return null;

}

... 這是個直接使用 JDBC進行查詢的程式,當中有幾個議題是值得探討的:

� 程式中的 SQL 語句 程式中直接撰寫 SQL 語句,SQL 是資料庫層面的語法,Java 則是個支援物件導向模型的程式語言,它們是兩種不同的語言模型,另一方面,在程式中穿插 SQL 一直有難以維護的問題。

� 資料物件的封裝 從資料庫查詢回來的資料,必須自己撰寫一些程式,才能將資料封裝為 User類別的一個實例,當您取得一個 User 物件時,並想將之儲存至資料庫中對應的表格時,也要一個一個取出 User 物件的資料成員,安插至 SQL 語句

Page 4: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�4

之中,再使用 JDBC 的 API 執行儲存動作,DataSourceDemo 專案中UserDAO 類別的 insert() 方法就進行了這些動作。

� 例外的處理 為了處理執行 JDBC 相關 API 時的例外,整個程式中充滿了 try...catch 例外處理邏輯,增加了程式撰寫時所花費的心力。

� Connection、Statement 的管理 您必須要注意到 Connection 使用完畢之後是否關閉,或是有設置連接池(Connection pool)的話,是否將 Connection 放回連接池,以及您也許會考慮到 Statement 要不要作快取、要不要關閉等,這些問題都要自行解決。 在第 5章中介紹了 Spring於 JDBC的封裝與簡化,當時介紹的內容可以解決上面所提到的一些議題,但是也有未解決的部份,例如在第 5章的一些專案範例中仍可以看到,在儲存 User物件時,要自行取得 User物件上的資料成員,安插至 SQL中以進行資料的儲存,從資料庫中查詢到資料時,也必須自己撰寫程式來將查詢到的資料封裝為 User物件的實例。 事實上由於物件模型與關聯模型的不同所引發的問題,還不只這些,例如在 Java中還有繼承、關聯、集合等的關係,它們在關聯式資料庫中要如何作對應,都還有更多的問題有待解決。

Hibernate是「物件/關係映射」(Object/Relational Mapping)的解決方案,簡寫為 ORM。簡單的說,Hibernate的作用之一,就是將 Java中的物件與物件關係,映射至關聯式資料庫中的表格與表格之間的關係,Hibernate提供了這個過程中自動映射轉換的方案。

2001 年未 Hibernate 第一個版本發表,2003 年 6 月 8 日 Hibernate 2發表,並於年末獲得 Jolt 2004大獎,後被 JBoss(http://www.jboss.com/)

Page 5: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6��

收納而成為其子項目之一,2005年 3月 Hibernate 3正式發表,這個小節中所要進行的簡介即是使用 Hibernate 3,看看如何使用 Hibernate來改寫第 5章中所設計過的 UserDAO類別,以初步了解 Hibernate的功能。

6.1.2 下載、設定 Hibernate 撰寫此書的時候, Hibernate 最新的版本是 3.2 GA( General

Availabity),請連接至 Hibernate的官方網站進行檔案的下載,網址是在http://hibernate.org/:

圖 6.1 Hibernate 官方網址 點選左邊的「Download」鏈結,可以進入至下載頁面,下載頁面中會有個「 Hibernate Core」的「 Download」鏈結連接至 Sourceforge

Page 6: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�6

( http://sourceforge.net/project/showfiles.php?group_id=40712&package_

id=127784):

圖 6.2 從 Sourceforge 下載 Hibernate 相關檔案 在 Sourceforge 上提供有幾種不同壓縮格式的下載檔案,您可以下載hibernate-3.2.0.ga.zip,選擇檔案的下載鏈結之後,會需要選擇鏡射(mirror)網站,可以選擇一個最接近下載所在區域的鏡射網站,以節省下載的時間。 下載 zip檔案並解壓縮之後,在解開的目錄下就有 hibernate3.jar,這是 Hibernate 3所需要的 API之封裝,可以在 doc目錄中找到 API說明或相關參考文件,在 lib目錄中的則是可能會用到的相依檔案。

Page 7: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�7

圖 6.3 hibernate-3.2 的內容 為了統一操作的環境,假設您的 Hibernate 相關檔案都是放在C:\workspace\library\hibernate-3.2,請參考圖 6.3 的內容。 對於將撰寫的第一個 Hibernate程式,所需要的 .jar檔案除了 hibernate3.jar之外,您還需要:

� antlr-2.7.6.jar

� asm.jar

� cglib-2.1.3.jar

� commons-collections-2.1.1.jar

� commons-logging-1.0.4.jar

� dom4j-1.6.1.jar

� ehcache-1.2.jar

� jta.jar

� log4j-1.2.11.jar

Page 8: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�8

Hibernate在底層仍是使用 JDBC,所以也必須要有 JDBC的驅動程式的 jar檔案,您可以參考第 2章中 2.1.2的內容,在 Eclipse中將 Hibernate相關 .jar設定至專案之中。

6.1.3 第一個 Hibernate 程式 直接來撰寫個專案,示範一下 Hibernate 的功能與使用方式,在這個示範中暫時不使用 Spring 的功能,稍後會再來改寫它,以了解 Spring 與Hibernate整合之後的方便性。 同樣的,先設計一個 User 類別,這個 User 類別將對應至 demo 資料庫中的一個 user表格,它只是一個簡單的 POJO(Plain Old Java Object):

HibernateDemo User.java

package onlyfun.caterpillar;

public class User {

private Integer id;

private String name;

private Integer age;

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;

}

Page 9: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6��

public Integer getAge() {

return age;

}

public void setAge(Integer age) {

this.age = age;

}

}

User類別必須有一個 id屬性,用來作為物件於程式中唯一性的識別,這個 id值可以由 Hibernate產生,通常對應至資料庫表格中的主鍵值。 在這邊使用的 MySQL 資料庫,表格建立時是使用以下的 SQL:

CREATE TABLE user (

id INT(11) NOT NULL auto_increment PRIMARY KEY,

name VARCHAR(100) NOT NULL default '',

age INT

) TYPE = InnoDB;

User類別與 user表格之間要建立對映關係,實際上是靠一個映射文件來完成,映射文件可以使用 XML來撰寫,通當命名為 .hbm.xml,例如設計一個 User.hbm.xml映射文件:

HibernateDemo User.hbm.xml

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE hibernate-mapping

PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="onlyfun.caterpillar.User" table="user">

Page 10: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�1�

<id name="id" column="id">

<generator class="native"/>

</id>

<property name="name" column="name"/>

<property name="age" column="age"/>

</class>

</hibernate-mapping> 在映射文件中,<class> 標籤用來定義類別與表格的對映,"name" 屬性用來設定類別名稱,"table" 屬性用來設定表格名稱;<id> 標籤中 "name" 屬性用來設定物件的唯一識別,通常對應至資料表中的主鍵欄位,也就是

"column" 屬性所設定的對應欄位;<generator> 的 "class" 用來設定主鍵的產生方式,設定為 "native" 表示依資料庫自己的主鍵生成方式來生成主鍵值,例如 user表格在建立時,主鍵設定為 auto_increment,所以會每加入一筆資料時都自動遞增主鍵值。 接下來的 <property> 標籤則設定 User物件的每一個屬性(由 "name" 屬性設定),分別對應至資料庫中 user表格的哪一個欄位(由 "column" 屬性設定)。 接著同樣的,設計一個 IUserDAO介面,讓應用程式在存取資料庫時,依賴於抽象的介面而非實際的類別實作,如下所示:

HibernateDemo IUserDAO.java

package onlyfun.caterpillar;

public interface IUserDAO {

public void insert(User user);

public User find(Integer id);

}

Page 11: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�11

然後由 UserDAO 類別來實作 IUserDAO 介面,這次實作時使用Hibernate的 API,需要注入一個 org.hibernate.SessionFactory物件:

HibernateDemo UserDAO.java

package onlyfun.caterpillar;

import org.hibernate.Hibernate;

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.Transaction;

public class UserDAO implements IUserDAO {

private SessionFactory sessionFactory;

public UserDAO() {

}

public UserDAO(SessionFactory sessionFactory) {

this.setSessionFactory(sessionFactory);

}

public void setSessionFactory(

SessionFactory sessionFactory) {

this.sessionFactory = sessionFactory;

}

public void insert(User user) {

// 取得 Session

Session session = sessionFactory.openSession();

// 開啟交易

Transaction tx= session.beginTransaction();

// 直接儲存物件

session.save(user);

// 送出交易

tx.commit();

session.close();

}

public User find(Integer id) {

Session session = sessionFactory.openSession();

Page 12: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�12

User user = (User) session.get(User.class, id);

Hibernate.initialize(user);

session.close();

return user;

}

} 在 Hibernate中,SessionFactory是用來管理 org.hibernate.Session,Session的作用類似於 JDBC中 Connection的作用,在 Hibernate中每次進行資料存取之前都必須開啟 Session,在進行資料變更或儲存時,則必須開啟交易,在儲存資料的時候,可以使用 Session的 save() 方法直接將物件儲存,在 org.hibernate.Transaction執行 commit() 方法之後,Hibernate會根據映射文件,將物件中的各個屬性資料轉換,並儲存至 user表格中對應的欄位。 在不使用資料庫連線的時候,要記得使用 close() 方法關閉 Session,關閉 Session並不等於關閉 Connection,在 Hibernate中如果有使用連接池(Connection pool),在 Session 的 close() 方法執行完畢後,會將Connection歸還至連接池之中,而不是直接關閉對資料庫的連結。 而在 UserDAO的 find() 方法實作中,可以直接使用 Session的 load() 方法,指定資料的 id值與要封裝資料的類別型態,接著查詢回來的物件中就包括了相對應的資料,將之轉換回 User型態,就可以操作 User物件上各種方法來取得資料。

Hibernate 3之後預設是啟用延遲加載(Lazy initialization)的功能,也就是真正在操作物件的 Getter取得資料前,不會向資料表格的對應欄位進行查 詢 , 而 此 時 Session 必 須 是 在 開 啟 的 狀 態 , 否 則 會 丟 出LazyInitializationException。在 find()方法結束前,已經關閉了 Session,

Page 13: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�13

為了操作傳回的物件以順利取得對應的欄位資料,必須先對 User 實例的每個屬性作初始化載入的動作,這可以透過 Hibernate.initialize()方法來完成。 在使用 Hibernate時,還必須定義一個設定檔放置在 Classpath之下,告知資料庫名稱、使用者名稱、密碼、映射文件位置等,可以使用 XML檔案來定義,檔名為 hibernate.cfg.xml,如下所示:

HibernateDemo hibernate.cfg.xml

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE hibernate-configuration PUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://hibernate.sourceforge.net/

→ hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<property name="show_sql">

true

</property>

<property name="dialect">

org.hibernate.dialect.MySQLDialect

</property>

<property name="connection.driver_class">

com.mysql.jdbc.Driver

</property>

<property name="connection.url">

jdbc:mysql://localhost/demo

</property>

<property name="connection.username">

caterpillar

</property>

<property name="connection.password">

123456

Page 14: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�14

</property>

<!-- 以下設置物件與資料庫表格映射文件 -->

<mapping resource="onlyfun/caterpillar/User.hbm.xml"/>

</session-factory>

</hibernate-configuration> 在 <session-factory> 的設定中,"show_sql" 被設定為"true”,表示在執行資料庫存取動作時顯示 Hibernate所生成、使用的 SQL語法;Hibernate可以使用許多不同的資料庫,不同的資料庫在 SQL 或操作上會有所差別,可以設定 "dialect" 來表示將使用哪一種資料庫適用的語句操作,由於這邊所 使 用 的 是 MySQL 資 料 庫 , 所 以 設 定 為 org.hibernate.dialect.

MySQLDialect;接著來的 "connection.driver_class"、"connection.url"、"connection.username"、"connection.password" 分別用以設定 JDBC 驅動程式類別、URL、使用者名稱與密碼。 在 <mapping> 中的 "resource" 設定的是物件與資料庫表格的映射文件位置,在上面的設定為 onlyfun/caterpillar/User.hbm.xml,表示檔案在Classpath路徑下的 onlyfun資料夾的 caterpillar資料夾下,通常 hbm.xml建議與對應的 POJO類別放置在一起。 接著就是建立 org.hibernate.cfg.Configuration 物件來讀取設定檔,它是 hibernate.cfg.xml中相關設定的具體代表物件,Configuration物件建立之後就不會變動,如果您修改了設定檔案的內容,則必須另外再產生新的 Configuration物件,新的變更才會被再次讀取。 由於 Hibernate 預設會在主控台下產生許多日誌訊息,為了方便觀看執行結果,可以在 Classpath 中放置一個 log4j.properties 檔案,設定只有警示訊息才加以顯示,例如:

Page 15: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�1�

HibernateDemo log4j.properties

log4j.rootLogger=WARN, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender

log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n 直接來看一個簡單的程式,了解如何使用以上設計的程式進行資料的儲存與查詢:

HibernateDemo HibernateDemo.java

package onlyfun.caterpillar;

import org.hibernate.SessionFactory;

import org.hibernate.cfg.Configuration;

public class HibernateDemo {

public static void main(String[] args) {

// Configuration 負責管理 Hibernate 配置訊息

Configuration config =

new Configuration().configure();

// 根據 config 建立 SessionFactory

// SessionFactory 將用於建立 Session

SessionFactory sessionFactory =

config.buildSessionFactory();

// 建立 DAO物件

IUserDAO userDAO = new UserDAO(sessionFactory);

User user = new User();

user.setName("caterpillar");

user.setAge(new Integer(30));

userDAO.insert(user);

user = userDAO.find(new Integer(1));

System.out.println("name: " + user.getName());

}

}

Page 16: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�16

在建立 Configuration物件之後,就可以使用它當中所包括的資訊來建立 SessionFactory物件,接著在建立 UserDAO時將 SessionFactory的實例指定給它,然後就可以操作 UserDAO 物件來進行資料的儲存了,一個執行的參考畫面如下所示:

圖 6.4 HibernateDemo 專案的執行結果 有關於 Hibernate更詳盡的介紹,可以參考我網站上的 Hibernate文件:

http://caterpillar.onlyfun.net/Gossip/HibernateGossip/

HibernateGossip.html

Page 17: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�17

6.2 在 Spring中整合 Hibernate

Spring整合了對 Hibernate的設定,並提供有 org.springframework.orm.

hibernate3.HibernateTemplate 等類別,讓您在結合 Hibernate 使用時可以簡化程式撰寫的流程,並且以與 JDBC 相類似的使用模型,提供使用Hibernate時的編程交易管理與宣告交易管理。

6.2.1 SessionFactory 注入

Spring可以與 Hibernate結合使用,Hibernate的連結、交易管理等是由建立 SessionFactory 開始的,SessionFactory 在應用程式中通常只需存在一個實例,因而 SessionFactory 底層的 DataSource 可以使用 Spring 的

IoC注入,之後再把 SessionFactory注入至相依的物件之中。 例如可以改寫一下 6.1.3 中 HibernateDemo 專案,首先可刪除hibernate.cfg.xml,因為這部份可以由 Spring在 Bean定義檔中撰寫 Data-

Source設定及依賴注入來取代,可以如下撰寫 Spring的 Bean定義檔:

SpringHibernateDemo beans-config.xml

<?xml version="1.0" encoding="UTF-8"?>

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

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

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="dataSource"

class="org.springframework.jdbc.

→ datasource.DriverManagerDataSource">

<property name="driverClassName"

value="com.mysql.jdbc.Driver"/>

<property name="url"

value="jdbc:mysql://localhost:3306/demo"/>

<property name="username" value="caterpillar"/>

<property name="password" value="123456"/>

Page 18: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�18

</bean>

<bean id="sessionFactory"

class="org.springframework.orm.

→ hibernate3.LocalSessionFactoryBean"

destroy-method="close">

<property name="dataSource" ref="dataSource"/>

<property name="mappingResources">

<list>

<value>onlyfun/caterpillar/User.hbm.xml</value>

</list>

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.MySQLDialect

</prop>

</props>

</property>

</bean>

<bean id="userDAO" class="onlyfun.caterpillar.UserDAO">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

</beans> 可以看到使用 Spring整合 Hibernate的好處,可以直接將 DataSource注入至 org.springframework.orm.hibernate3.LocalSessionFactoryBean 中,至於 Hibernate所需的相關設定,則可透過 LocalSessionFactoryBean的相關屬性來設定,像是設定資料庫名稱、使用者名稱、密碼等,LocalSession-

FactoryBean會建立 SessionFactory的實例,並在執行依賴注入時將這個實例設定給 UserDAO。

Hibernate 的物件與關聯表格的映射文件之位置與名稱,則指定於

"mappingResources" 屬性中,如果自行提供 Hibernate 本身的設定檔(hibernate.cfg.xml),也可以使用 "configLocation" 屬性來指定設定檔

Page 19: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�1�

的位置,而這邊則使用 "hibernateProperties" 屬性在 Spring的 Bean定義檔中直接指定,可以藉此減少對 XML組態檔案的管理。

Page 20: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�2�

注意!在 Spring 2.0 中可以選擇要整合 Hibernate 2 或Hibernate 3(Hibernate 3 在套件名稱上與 Hibernate 2 是不相容的),因而在 Spring 中整合時會有兩種版本的 API。

org.springframework.orm.hibernate.LocalSessionFactoryBean是在整合 Hibernate 2 時所使用的類別,而 org.spring-

framework.orm.hibernate3.LocalSessionFactoryBean 則是在整合 Hibernate 3 時使用的類別,因為主要類別名稱是相同的(例如 LocalSessionFactoryBean),所以撰寫時要注意套件名稱的設定是否正確。 專案中剩下的部份都不用修改,可以撰寫一個簡單的測試程式,來看看如何使用 Spring整合 Hibernate,進行簡單的資料存取:

SpringHibernateDemo SpringHibernateDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.

support.ClassPathXmlApplicationContext;

public class SpringHibernateDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

// 建立 DAO物件

IUserDAO userDAO =

(IUserDAO) context.getBean("userDAO");

User user = new User();

user.setName("caterpillar");

user.setAge(new Integer(30));

userDAO.insert(user);

Page 21: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�21

user = userDAO.find(new Integer(1));

System.out.println("name: " + user.getName());

}

} 執行的結果基本上與 HibernateDemo 專案是相同的,可以參考圖 6.4的執行畫面。 在 SpringHibernateDemo 專案中,如果您不是選擇加入spring.jar 的話,則必須個別加入以下的檔案至 Classpath 路徑之中:sping-core.jar、spring-beans.jar、spring-context.jar、spring-jdbc.jar、spring-dao.jar、spring-hibernate.jar。 在映射文件的指定上,除了使用 "mappingResources" 屬性設定之外,也可以使用 "mappingDirectoryLocations" 屬性設定,一次指定某個路徑下的所有 .hbm.xml檔案,例如:

...

<bean id="sessionFactory"

class="org.springframework.orm.hibernate3.

→ LocalSessionFactoryBean"

destroy-method="close">

<property name="dataSource" ref="dataSource"/>

<property name="mappingDirectoryLocations">

<list>

<value>classpath:/onlyfun/caterpillar</value>

</list>

</property>

...

</bean>

...

Page 22: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�22

6.2.2 HibernateTemplate

Spring 的 Template-callback 機制在 Hibernate 的實現上,提供有org.springframework.orm.hibernate3.HibernateTemplate 類別與 org.

springframework.orm.hibernate3.HibernateCallback介面,一個使用的例子如下:

HibernateTemplate hibernateTemplate =

new HibernateTemplate(sessionFactory);

...

hibernateTemplate.execute(new HibernateCallback() {

public Object doInHibernate(

Session session) throws HibernateException {

return session.load(User.class);

}

}); 執行完畢之後,Session 物件會自動關閉。HibernateTemplate 上也提供有數個方便的方法,在執行時自動建立 HibernateCallback 物件,例如load()、get()、save、delete() 等方法,可以改寫 SpringHibernateDemo專案中的 UserDAO類別,使用 HibernateTemplate來簡化程式的撰寫:

HibernateTemplateDemo UserDAO.java

package onlyfun.caterpillar;

import org.hibernate.SessionFactory;

import org.springframework.orm.hibernate3.HibernateTemplate;

public class UserDAO implements IUserDAO {

private HibernateTemplate hibernateTemplate;

public void setSessionFactory(

SessionFactory sessionFactory) {

hibernateTemplate =

new HibernateTemplate(sessionFactory);

}

Page 23: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�23

public void insert(User user) {

hibernateTemplate.save(user);

}

public User find(Integer id) {

User user = (User) hibernateTemplate.get(User.class, id);

return user;

}

} 其它的檔案並不需要修改,執行的結果也是相同的。 您必須在 Classpath 中加入 spring-orm.jar 檔案才可以執行HibernateTemplateDemo 專案中的程式。 您可以繼承 org.springframework.orm.hibernate3.support.Hibernate-

DaoSupport類別來撰寫 UserDAO類別,這可以省去一些管理 SessionFactory、HibernateTemplate 資源的工作,只要注入 SessionFactory 的實例就可以了,例如將上面的 UserDAO替代為以下的內容,則程式也是可以執行:

package onlyfun.caterpillar;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class UserDAO extends HibernateDaoSupport implements IUserDAO {

public void insert(User user) {

getHibernateTemplate().save(user);

}

public User find(Integer id) {

User user = (User) getHibernateTemplate().get(User.class, id);

Page 24: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�24

return user;

}

} 注意到,在這邊都是使用 HibernateTemplate 的 get()方法,如果是使用 HibernateTemplate的 load()方法,將會使用到 Session物件的 load()方法,這將會使用 Hibernate 3預設的延遲加載功能,但 HibernateTemplate執行完 load()方法之後,將會直接關閉 Session,因而此時若再嘗試取得User的屬性時,將會發生 LazyInitializationException。 解決的方法之一,是實作 HibernateCallback 介面,並將實例傳入給HibernateTemplate的 execute()方法,例如:

...

public User find(final Integer id) {

User user = (User) hibernateTemplate.execute(

new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

User user = (User) session.load(User.class, id);

Hibernate.initialize(user);

return user;

}

});

return user;

} 在 doInHibernate()方法的實作中,主動使用 Hibernate.initialize()方法初始化 User物件的屬性。另一個方法,就是直接在 Hibernate的.hbm.xml中設定 "lazy"屬性為 "false",表示不使用延遲加載功能,例如修改User.hbm.xml:

...

<hibernate-mapping>

Page 25: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�2�

<class name="onlyfun.caterpillar.User" table="user" lazy="false">

...

</class>

</hibernate-mapping>

6.2.3 HibernateTemplate 的 Lob 支援 在使用 Spring搭配 Hibernate時,可以簡化對 Lob型態的處理,只要在 SessionFactory建構時指定 LobHandler注入,並將 SessionFactory注入給 HibernateTemplate,例如:

...

<bean id="lobHandler" class="org.springframework.jdbc.

→ support.lob.DefaultLobHandler"/>

<bean id="sessionFactory" class="org.springframework.orm.

→ hibernate3.LocalSessionFactoryBean"

destroy-method="close">

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

<property name="lobHandler" ref=”lobHandler”/>

<property name="mappingResources">

<list>

<value>onlyfun/caterpillar/User.hbm.xml</value>

</list>

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.MySQLDialect

</prop>

</props>

</property>

</bean>

<bean id="hibernateTemplate" class="org.springframework.orm.

→ hibernate3.HibernateTemplate">

<property name="sessionFactory" ref=”sessionFactory”/>

</bean>

...

Page 26: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�26

在這邊指定 LobHandler時,對於 MySQL、DB2、MS SQL Server、Oracle 10g,使用 DefaultLobHandler即可,而對於 Oracle 9i,則可以使用OracleLobHandler。 接下來的操作與一般對 HibernateTemplate 的操作無異,例如您的資料庫表格為:

CREATE TABLE user (

id INT auto_increment PRIMARY Key,

txt TEXT,

image BLOB

);

Spring 的 ClobStringType 可以將 Clob 映射至 String,而 BlobByte-

ArrayType可以將 Blob映射至 byte[],可以設計一個 User類別如下:

package onlyfun.caterpillar;

public class User {

private Integer id;

private String txt;

private byte[] image;

public Integer getId() {

return id;

}

public void setId(Integer id) {

this.id = id;

}

public byte[] getImage() {

return image;

}

public void setImage(byte[] image) {

this.image = image;

}

public String getTxt() {

return txt;

}

Page 27: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�27

public void setTxt(String txt) {

this.txt = txt;

}

} 至於 Use.hbm.xml則可以如下撰寫:

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE hibernate-mapping

PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="onlyfun.caterpillar.User" table="user">

<id name="id" column="id">

<generator class="native"/>

</id>

<property name="txt" column="txt"/>

<property name="image" column="image"/>

</class>

</hibernate-mapping> 以下是個簡單的儲存與讀取 Lob的程式片段示範:

...

ApplicationContext context =

new FileSystemXmlApplicationContext("beans-config.xml");

InputStream is = new FileInputStream(

new File("c:\\workspace\\wish.jpg"));

byte[] b = new byte[is.available()];

is.read(b);

is.close();

User user = new User();

user.setTxt("long...long...text");

user.setImage(b);

HibernateTemplate hibernateTemplate =

(HibernateTemplate) context.getBean("hibernateTemplate");

hibernateTemplate.save(user);

Page 28: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�28

user = (User) hibernateTemplate.execute(new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

User user = (User) session.load(User.class, new Integer(1));

Hibernate.initialize(user);

return user;

}

});

System.out.println(user.getTxt());

b = user.getImage();

OutputStream os = new FileOutputStream(

new File("c:\\workspace\\wish_bak.jpg"));

os.write(b);

os.close();

...

6.2.4 Hibernate 編程交易管理

Hibernate 提供有自己的交易管理實作,然而在使用 Spring 整合Hibernate 時,建議將交易管理交由 Spring 來負責,您可以使用編程式的交易管理,方法與第 5章中直接使用 JDBC進行編程式的交易管理類似,例如可將 HibernateTemplateDemo 專案中的 UserDAO 類別加以改寫,使之具有交易管理功能。

TransactionTemplateDemo UserDAO.java

package onlyfun.caterpillar;

import org.hibernate.SessionFactory;

import org.springframework.dao.DataAccessException;

import org.springframework.orm.hibernate3.HibernateTemplate;

import org.springframework.transaction.TransactionDefinition;

import org.springframework.transaction.TransactionStatus;

import org.springframework.transaction.

support.TransactionCallbackWithoutResult;

import org.springframework.transaction.support.TransactionTemplate;

Page 29: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�2�

import org.springframework.orm.

hibernate3.HibernateTransactionManager;

public class UserDAO implements IUserDAO {

private TransactionTemplate transactionTemplate;

private HibernateTemplate hibernateTemplate;

public void setSessionFactory(

SessionFactory sessionFactory) {

this.transactionTemplate =

new TransactionTemplate(

new HibernateTransactionManager(

sessionFactory));

this.hibernateTemplate =

new HibernateTemplate(sessionFactory);

}

public void insert(User user) {

final User userData = user;

// 設定傳播行為

transactionTemplate.setPropagationBehavior(

TransactionDefinition.PROPAGATION_REQUIRED);

transactionTemplate.execute(

new TransactionCallbackWithoutResult() {

protected void doInTransactionWithoutResult(

TransactionStatus status) {

hibernateTemplate.save(userData);

}

});

}

public User find(Integer id) {

User user = (User) hibernateTemplate.get(User.class, id);

return user;

}

}

org.springframework.transaction.support.TransactionTemplate類別在建立時,需要一個實作 PlatformTransactionManager 的實例,這邊使

Page 30: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�3�

用 的 是 org.springframework.orm.hibernate3.HibernateTransaction-

Manager 類別來建立實例,TransactionTemplate 必須設定傳播行為(Propagation Behavior)、並使用 Callback物件來執行交易,在發生例外時將會撤消操作,如果需要細部控制何時撤消操作,可以在捕捉例外之後,使用 TransactionStatus 的 setRollbackOnly()方法撤消操作,這些動作與第 5章講解 JDBC交易管理時都是類似的,可以參考一下第 5章有關交易管理的內容,或看看 Spring參考文件來了解一些細節說明。

6.2.5 Hibernate 宣告交易管理

Spring對 Hibernate提供宣告交易管理,這與第 5章中介紹過的 JDBC宣告交易管理類似,先來介紹一下比較簡單的方式,直接從 Hibernate-

TemplateDemo專案來進行改寫,事實上不需要修改 UserDAO類別,而只要在定義檔上設定即可:

HibernateDeclarativeTransactionDemo UserDAO.java

<?xml version="1.0" encoding="UTF-8"?>

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

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

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

<bean id="dataSource"

class="org.springframework.jdbc.

→ datasource.DriverManagerDataSource">

<property name="driverClassName"

value="com.mysql.jdbc.Driver"/>

<property name="url"

value="jdbc:mysql://localhost:3306/demo"/>

<property name="username" value="caterpillar"/>

<property name="password" value="123456"/>

</bean>

<bean id="sessionFactory"

class="org.springframework.orm.

Page 31: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�31

→ hibernate3.LocalSessionFactoryBean"

destroy-method="close">

<property name="dataSource" ref="dataSource"/>

<property name="mappingResources">

<list>

<value>onlyfun/caterpillar/User.hbm.xml</value>

</list>

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.MySQLDialect

</prop>

</props>

</property>

</bean>

<bean id="userDAO" class="onlyfun.caterpillar.UserDAO">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

<bean id="transactionManager"

class="org.springframework.orm.

→ hibernate3.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

<bean id="userDAOProxy"

class="org.springframework.transaction.

→ interceptor.TransactionProxyFactoryBean">

<property name="transactionManager"

ref="transactionManager"/>

<property name="proxyInterfaces">

<list>

<value>onlyfun.caterpillar.IUserDAO</value>

</list>

</property>

<property name="target" ref="userDAO"/>

<property name="transactionAttributes">

<props>

<prop key="insert">PROPAGATION_REQUIRED</prop>

</props>

</property>

Page 32: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�32

</bean>

</beans> 主 要 的 差 別 在 於 設 定 檔 中 建 立 了 "transactionManager" 與"userDAOProxy",由於 Spring 所提供的一致性的設定模型,所以設定的方式與第 5章中介紹 JDBC宣告交易管理的設定方式是類似的,可以參考一下第 5章有關於宣告交易管理的說明。 來寫個程式測試一下,這次取回 DAO代理物件來進行操作,如下所示:

HibernateDeclarativeTransactionDemo SpringHibernateDemo.java

package onlyfun.caterpillar;

import org.springframework.context.ApplicationContext;

import org.springframework.context.

support.ClassPathXmlApplicationContext;

public class SpringHibernateDemo {

public static void main(String[] args) {

ApplicationContext context =

new ClassPathXmlApplicationContext(

"beans-config.xml");

// 建立 DAO物件

IUserDAO userDAO =

(IUserDAO) context.getBean("userDAOProxy");

User user = new User();

user.setName("cater");

user.setAge(new Integer(30));

userDAO.insert(user);

user = userDAO.find(new Integer(1));

System.out.println("name: " + user.getName());

}

}

Page 33: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�33

如果在執行 insert() 方法時發生了例外,則資料不會被新增至表格中,您可以自行在 UserDAO 類別中嘗試丟出個例外,以測試交易的功能是否正確執行。 同樣的,在 Spring 中對 Hibernate 進行宣告交易管理時,使用到 Spring AOP 的功能,所以您必須在 Classpath 的路徑設定中加入 spring-aop.jar檔案才可以執行專案中的程式。 在 5.3.6介紹過 Spring 2.0基於 XML Schema的宣告交易管理,在這邊若要使用,則只要修改一下 beans-config.xml為以下設定方式:

HibernateDeclarativeTransactionDemo2 beans-config.xml

<?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:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/aop

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

http://www.springframework.org/schema/tx

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

<bean id="dataSource"

class="org.springframework.jdbc.

→ datasource.DriverManagerDataSource">

<property name="driverClassName"

value="com.mysql.jdbc.Driver"/>

<property name="url"

value="jdbc:mysql://localhost:3306/demo"/>

Page 34: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�34

<property name="username" value="caterpillar"/>

<property name="password" value="123456"/>

</bean>

<bean id="sessionFactory"

class="org.springframework.orm.

→ hibernate3.LocalSessionFactoryBean"

destroy-method="close">

<property name="dataSource" ref="dataSource"/>

<property name="mappingResources">

<list>

<value>onlyfun/caterpillar/User.hbm.xml</value>

</list>

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.MySQLDialect

</prop>

</props>

</property>

</bean>

<bean id="userDAO" class="onlyfun.caterpillar.UserDAO">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

<bean id="transactionManager"

class="org.springframework.orm.

→ hibernate3.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

<tx:advice id="txAdvice"

transaction-manager="transactionManager">

<tx:attributes>

<tx:method name="insert" propagation="REQUIRED"/>

<tx:method name="find" read-only="true"/>

</tx:attributes>

</tx:advice>

<aop:config>

<aop:pointcut id="userDAOPointcut" expression=

Page 35: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�3�

"execution(* onlyfun.caterpillar.IUserDAO.*(..))"/>

<aop:advisor advice-ref="txAdvice"

pointcut-ref="userDAOPointcut"/>

</aop:config>

</beans> 由於不再於設定檔中設定代理物件,所以直接取得"userDAO"實例進行操作即可。您也可以使用基於 Annotation 的設定方式,例如改寫 User-

DAO如下:

HibernateDeclarativeTransactionDemo3 UserDAO.java

package onlyfun.caterpillar;

import org.hibernate.SessionFactory;

import org.springframework.orm.hibernate3.HibernateTemplate;

import org.springframework.transaction.annotation.Propagation;

import org.springframework.transaction.annotation.Transactional;

public class UserDAO implements IUserDAO {

private HibernateTemplate hibernateTemplate;

public void setSessionFactory(

SessionFactory sessionFactory) {

hibernateTemplate =

new HibernateTemplate(sessionFactory);

}

@Transactional(propagation = Propagation.REQUIRED)

public void insert(User user) {

hibernateTemplate.save(user);

}

@Transactional(readOnly=true)

public User find(Integer id) {

User user = (User) hibernateTemplate.get(User.class, id);

return user;

}

}

Page 36: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�36

接著在 beans-config.xml 中使用<tx:annotation-driven>並設定 Tran-

sactionManager即可,例如:

HibernateDeclarativeTransactionDemo3 beans-config.xml

<?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:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-2.0.xsd

http://www.springframework.org/schema/tx

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

<bean id="dataSource"

class="org.springframework.jdbc.

→ datasource.DriverManagerDataSource">

<property name="driverClassName"

value="com.mysql.jdbc.Driver"/>

<property name="url"

value="jdbc:mysql://localhost:3306/demo"/>

<property name="username" value="caterpillar"/>

<property name="password" value="123456"/>

</bean>

<bean id="sessionFactory"

class="org.springframework.orm.

→ hibernate3.LocalSessionFactoryBean"

destroy-method="close">

<property name="dataSource" ref="dataSource"/>

<property name="mappingResources">

<list>

<value>onlyfun/caterpillar/User.hbm.xml</value>

</list>

</property>

<property name="hibernateProperties">

<props>

<prop key="hibernate.dialect">

org.hibernate.dialect.MySQLDialect

Page 37: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�37

</prop>

</props>

</property>

</bean>

<bean id="userDAO" class="onlyfun.caterpillar.UserDAO">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

<bean id="transactionManager"

class="org.springframework.orm.

→ hibernate3.HibernateTransactionManager">

<property name="sessionFactory" ref="sessionFactory"/>

</bean>

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

</beans>

Page 38: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Spring 2.0 技術手冊(林信良 – http://openhome.cc)

6�38

6.3 接下來的主題 對 Spring 於持久層的支援,暫且說明到這邊,在 Spring 的參考文件中,還有更多有關於持久層方案的說明,建議您也可以參考一下,在下一個章節開始,將來介紹一下 Spring 於 Web 層所提供的解決方案,首先將先認識 Spring所提供的 MVC框架,使用它的好處是易於整合使用 Spring的 IoC容器功能。

圖 6.5 Spring 整合並簡化了 Hibernate 的使用方式

Page 39: Spring 2.0 技術手冊第六章 - Hibernate 與 Spring

Chapter 6 Hibernate 與 Spring

6�3�