posts - 262,  comments - 221,  trackbacks - 0

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 阅读(1125) 评论(0)  编辑  收藏 所属分类: J2SE

只有注册用户登录后才能发表评论。


网站导航:
 
<2009年12月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用链接

留言簿(19)

随笔分类

随笔档案

BlogJava热点博客

好友博客

搜索

  •  

最新评论

阅读排行榜

评论排行榜