Informa的hibernate包提供的是基于Hibernate的接口實現,這意味著從這個包獲取的任意對象必須都是持久化的狀態或者可以被持久化的。其中的關鍵就是ChannelBuilder類和SessionHandler類
與impl.basic包中的ChannelBuilder類不同,impl.hibernate包中的ChannelBuilder依賴于Hibernate來完成對象的構建和訪問。對于這個類必須注意的是“它是線程不安全的”
Factory for the creation of the channel object model with the hibernate persistent store.
NOT THREAD SAFE
Hibernate Multi-threading notes: ChannelBuilder has some subtleties as it relates to threading. The specifics of the way it is supported still need to be proven. Certainly the error handling here and in UpdateChannelTask and in ChannelRegistry is incomplete. It seems to work, but I would consider it incomplete still.
The key facts are
(1) Sessions are not thread safe and
(2) Sessions should have relatively short lifespans.
原因作者在上面已經提到了因為ChannelBuilder依賴于Hibernate的Session,而Session又是線程不安全的。如果在創建出ChannelBuilder后不小心傳遞給不恰當的對象,就會造成不恰當的持久化。針對此問題,作者提出了自己的解決方法:
To support this, there is a mode of using ChannelBuilder where it holds on to a SessionHandler and manages the creation and destruction of Sessions on behalf of the caller. When you supply a SessionHandler to ChannelBuilder, you may use the beginTransaction() and endTransaction() calls to take all the steps needed before and after a transaction. At the end of endTransaction() the transaction will be closed and the session will be flushed and closed. To use this mode, you should
(1) Create a SessionHandler ,
(2) Create a JDBC Connection to the database,
(3) sessionHandler.setConnection(connection), and
(4) use new ChannelBuilder(sessionHandler).
根據作者的說法,我們應該首先創建一個SessionHandler對象,那么這個SessionHandler又是什么東西?其實SessionHandler類似于Hibernate提供的HibernateUtil類,用于獲取session對象,也是一個單例模式。但和HibernateUtil不同,SessionHandler不會把session保存到ThreadLocal中,而是交由ChannelBuilder保存。
★SessionHandler
在SessionHandler中維護了以下變量
private static SessionHandler myInstance;

private Configuration cfg;
private SessionFactory sessFactory;
private Connection conn;
private Session curSession;
SessionHandler可以根據外界傳遞的java.sql.Connection對象參數來動態創建session,也可以根據初始化時從hibernate.cfg.xml文件中讀入的信息來默認創建并返回

public void setConnection(Connection connection)
{
conn = connection;
}


public Session getSession() throws HibernateException
{
if (conn != null)
// use specific connection information
curSession = sessFactory.openSession(conn);
else
// use connection information from xml file
curSession = sessFactory.openSession();
return curSession;
}

public Session getSession(Connection conn)
{
this.conn = conn;
curSession = sessFactory.openSession(conn);
return curSession;
}

注意:SessionHandler因為不會把Session綁定到線程本地變量,所以絕對不能夠有Session的緩存,但是可以有Connection的緩存。
在了解了SessionHandler的作用后,再會過頭來看上面作者提到的解決線程安全的方法。SessionHandler已經解決了前三步,最后我們必須把這個SessionHandler作為參數傳遞給ChannelBuilder的構造函數。
★ChannelBuilder

public ChannelBuilder(Session session)
{
logger.info("New Channel Builder for: " + session);
this.session = session;
this.handler = null;
}


/** *//**
* ChannelBuilder constructor. ChannelBuilder will manage sessions and
* transactions. Supplied SessionHandler needs to have a live JDBC connection
* available.
*/

public ChannelBuilder(SessionHandler handler)
{
logger.debug("New Channel Builder for: " + handler);
this.handler = handler;
this.session = null;
}
可以看到ChannelBuilder的構造函數有兩個重載版本,一個是接收SessionHandler的,一個是直接接受Session的。對于后者ChannelBuilder必須自己維護Session的生命周期,在存在事務的情況下還必須自己管理事務。顯然這個太麻煩了。于是ChannelBuilder中提供了幾個方法來幫助管理Session和事務。
開始事務

/** *//**
* Processing needed at the start of a transaction. - creating a session -
* beginning the transaction
*/

public void beginTransaction() throws ChannelBuilderException
{
logger.info("beginTransaction");
// Session should be create by handler
if (session != null || handler == null)
throw new IllegalStateException(
"Session != null || handler == null");

try
{
// Get Session from handler and open transaction
session = handler.getSession();
transaction = session.beginTransaction();

} catch (HibernateException e)
{
e.printStackTrace();
transaction = null;
throw new ChannelBuilderException(e);
}
}
上面的方法有2個需要注意的地方:
A.Session必須由SessionHandler來獲取,如果使用第一種構造方法此時調用會出錯
B.Session只能在Transaction開始時才打開,在構造函數執行時依然是null的,避免浪費資源
結束事務

/** *//**
* Processing needed at the end of a transaction. - commit the transaction -
* flush the session - close the session TODO: catch the exception so this
* method doesn't have any throws.
*/

public void endTransaction() throws ChannelBuilderException
{
logger.info("endTransaction");
if (handler == null || transaction == null || session == null)
throw new IllegalStateException(
"handler == null || transaction == null || session == null");

try
{
// Why flush after commit transaction ?
transaction.commit();
session.flush();
session.close();
session = null;
transaction = null;


} catch (HibernateException he)
{
if (transaction != null)

try
{
he.printStackTrace();
transaction.rollback();
transaction = null;

if (session.isOpen())
{
session.close();
session = null;
}

} catch (HibernateException e)
{

if (session.isOpen())
{
session = null;
}
e.printStackTrace();
throw new ChannelBuilderException(e);
}
throw new ChannelBuilderException(he);
}
}
這個方法我有一個不懂的地方:為什么是commit在先,再flush的?這個需要再和作者探討。
ChannelBuilder允許我們重設事務,即提交現有事務并刷新緩存,之后注銷transaction和session。這一點我認為適合于在一個“長事務”的過程中,切換到不同的事務上下文環境。
重置事務

public void resetTransaction()
{
logger.debug("Transaction being reset.");
// Transaction already start, should commit or rollback
// first, then close the session if it's open

if (transaction != null)
{

try
{
transaction.commit();
transaction = null;

} catch (HibernateException e)
{
// Why don't need to rollback the transation
transaction = null;
e.printStackTrace();
}
}

if (session != null)
{

try
{
session.flush();
session.close();
session = null;

} catch (HibernateException e)
{
e.printStackTrace();
session = null;
}
}
}
這個方法里同樣也有我認為不妥的地方,當事務提交失敗后沒有回滾而是直接transaction=null。
由于ChannelBuilder的事務控制方法依賴于SessionHandler,Session只能在事務的開始獲取,在結束后銷毀。所以另外一個方法可用來判斷當前事務是否正在進行中。
判斷事務是否進行中

/** *//**
* Check if we are already in the middle of a transaction. This is needed
* because as of now begin/endTransactions cannot be nested and in fact give
* assert errors if you try.
*
* @return - boolean indicating whether we are currently in a transaction.
*/

public boolean inTransaction()
{
return session != null && transaction != null;
}
很明顯的,對于不是從SessionHandler處獲取的session,其transaction必定是null的。所以這個方法并不能用于構造方法一的情況,其次就像API所講的,由于事務是可以嵌套的,所以這里返回false,不代表當前Session就不是處在事務環境下,它可能是屬于別處的事務的。目前這個方法只能用于由ChannelBuilder創建的事務。
當事務開始后,我們可以開始獲取session對象了。
獲取Session

/** *//**
* Certain Hibernate calls require the session. Note that this call should
* only be made between a beginTransaction and endTransaction call which is
* why we throw an IllegalStateException otherwise.
*/

public Session getSession()
{
if (handler == null || session == null)
throw new IllegalStateException(
"getSession must be bracketed by begin/endTransaction");
if (!handler.isSessionOpen())
throw new IllegalStateException("Hibernate Handler must be open");
return session;
}

/** *//**
* Returns true if session is open.
*/

public boolean isSessionOpen()
{
return curSession.isOpen();
}
如果對于第一種構造方法這當然是返回false,所以getSession只能用在beginTransaction和endTransaction的調用之間,否則會直接拋出異常。這強制我們必須為每次的數據庫操作開啟一個事務(顯式地調用beginTransaction)。
★Session的安全性
下面我們回到最初的問題:ChannelBuilder和Session如何保證session的線程安全?
我們也許還有一個疑問:SessionHandler中不是有一個curSession成員變量嗎?而且這個類還是一個單例模式,還作為ChannelBuilder的成員變量。那么不同的ChannelBuilder會不會錯用了這個session呢?
這個擔心是多余的,因為在session只在事務開始時才就被創建并賦予ChannelBuilder,此后ChannelBuilder會對session進行緩存,后續的使用這個緩存session,而不是SessionHandler中的。
其次,如果仔細觀察這個類,我們會發現ChannelBuilder這個類里面的session是只讀的,也就是它只在構造方法被調用時,或者事務開始時被設定,之后在事務運行過程中它沒有對應的setSession(Session anotherSession)方法來改變session。而且這個session是保存在ChannelBuilder中的私有成員變量,這意味著即便有不同的線程同時操作、訪問這個類的實例,他們所使用的session都是同一個的。
在ChannelBuilder中提供了三個持久化的方法,分別用于持久化各種channel object。

protected void save(Object dataObject)
{
if (session == null)
throw new IllegalStateException("Session == null");

try
{
session.save(dataObject);

} catch (HibernateException he)
{
throw new RuntimeException(he.getMessage());
}
}


public void update(Object o) throws ChannelBuilderException
{

try
{
session.update(o);

} catch (HibernateException e)
{
e.printStackTrace();
throw new ChannelBuilderException("update() Failed");
}
}


public void delete(Object o) throws ChannelBuilderException
{

try
{
session.delete(o);

} catch (HibernateException e)
{
e.printStackTrace();
throw new ChannelBuilderException("delete() Failed");
}
}
請注意這里:如果采用構造方法一,那么你將需要在執行這些方法后手動提交事務。如果你認為你的操作不需要事務那么當然可以不用。
★創建channel object
在ChannelBuilder里面,數量最多的方法就是creatXxx(Param1, Param2...)這一類的方法。例如下面的createChannel方法

/** *//**
* Try to get unique object from database if any record matches the
* location string. Otherwise create it at memory first and then save
* to database.
*
* Channel returned by this method will be synchronized by Hibernate
* automatically since now it's persistent state
*
* May throw runtime HibernateException
*/
public ChannelIF createChannel(Element channelElement, String title,

String location)
{
ChannelIF obj = null;

if (location != null)
{
Query query = session
.createQuery("from Channel as channel where channel.locationString = ? ");
query.setString(0, location);
obj = (ChannelIF) query.uniqueResult();
}

if (obj == null)
{
obj = new Channel(channelElement, title, location);
session.save(obj);

} else
{
logger
.info("Found already existing channel instance with location "
+ location);
}
return obj;
}
ChannelBuilder對channel object的創建原則就是:
A.如果能夠從持久層中找到對應的記錄,那么從持久層返回
B.如果找不到,則創建它并持久化它,然后返回該對象(已持久化)
只要記得的一點就是:從ChannelBuilder返回的對象都是已經持久化的。比如channel,此時我們甚至可以通過channel.getItems()來獲得其下屬的各個news item。
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。
posted on 2009-12-28 15:57
Paul Lin 閱讀(1240)
評論(0) 編輯 收藏 所屬分類:
J2SE