posts - 262,  comments - 221,  trackbacks - 0

          Informa的hibernate包提供的是基于Hibernate的接口實(shí)現(xiàn),這意味著從這個(gè)包獲取的任意對(duì)象必須都是持久化的狀態(tài)或者可以被持久化的。其中的關(guān)鍵就是ChannelBuilder類和SessionHandler類

          與impl.basic包中的ChannelBuilder類不同,impl.hibernate包中的ChannelBuilder依賴于Hibernate來完成對(duì)象的構(gòu)建和訪問。對(duì)于這個(gè)類必須注意的是“它是線程不安全的”

          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.

          原因作者在上面已經(jīng)提到了因?yàn)镃hannelBuilder依賴于Hibernate的Session,而Session又是線程不安全的。如果在創(chuàng)建出ChannelBuilder后不小心傳遞給不恰當(dāng)?shù)膶?duì)象,就會(huì)造成不恰當(dāng)?shù)某志没a槍?duì)此問題,作者提出了自己的解決方法:

          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).


          根據(jù)作者的說法,我們應(yīng)該首先創(chuàng)建一個(gè)SessionHandler對(duì)象,那么這個(gè)SessionHandler又是什么東西?其實(shí)SessionHandler類似于Hibernate提供的HibernateUtil類,用于獲取session對(duì)象,也是一個(gè)單例模式。但和HibernateUtil不同,SessionHandler不會(huì)把session保存到ThreadLocal中,而是交由ChannelBuilder保存。

          ★SessionHandler

          在SessionHandler中維護(hù)了以下變量

              private static SessionHandler myInstance;

              
          private Configuration cfg;
              
          private SessionFactory sessFactory;
              
          private Connection conn;
              
          private Session curSession;

          SessionHandler可以根據(jù)外界傳遞的java.sql.Connection對(duì)象參數(shù)來動(dòng)態(tài)創(chuàng)建session,也可以根據(jù)初始化時(shí)從hibernate.cfg.xml文件中讀入的信息來默認(rèn)創(chuàng)建并返回

            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因?yàn)椴粫?huì)把Session綁定到線程本地變量,所以絕對(duì)不能夠有Session的緩存,但是可以有Connection的緩存。

          在了解了SessionHandler的作用后,再會(huì)過頭來看上面作者提到的解決線程安全的方法。SessionHandler已經(jīng)解決了前三步,最后我們必須把這個(gè)SessionHandler作為參數(shù)傳遞給ChannelBuilder的構(gòu)造函數(shù)。

          ★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的構(gòu)造函數(shù)有兩個(gè)重載版本,一個(gè)是接收SessionHandler的,一個(gè)是直接接受Session的。對(duì)于后者ChannelBuilder必須自己維護(hù)Session的生命周期,在存在事務(wù)的情況下還必須自己管理事務(wù)。顯然這個(gè)太麻煩了。于是ChannelBuilder中提供了幾個(gè)方法來幫助管理Session和事務(wù)。

          開始事務(wù)
          /**
               * 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個(gè)需要注意的地方:
           A.Session必須由SessionHandler來獲取,如果使用第一種構(gòu)造方法此時(shí)調(diào)用會(huì)出錯(cuò)
           B.Session只能在Transaction開始時(shí)才打開,在構(gòu)造函數(shù)執(zhí)行時(shí)依然是null的,避免浪費(fèi)資源


          結(jié)束事務(wù)

              /**
               * 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);
                  }

              }

          這個(gè)方法我有一個(gè)不懂的地方:為什么是commit在先,再flush的?這個(gè)需要再和作者探討。

          ChannelBuilder允許我們重設(shè)事務(wù),即提交現(xiàn)有事務(wù)并刷新緩存,之后注銷transaction和session。這一點(diǎn)我認(rèn)為適合于在一個(gè)“長事務(wù)”的過程中,切換到不同的事務(wù)上下文環(huán)境。

          重置事務(wù)

              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;
                      }

                  }

              }

          這個(gè)方法里同樣也有我認(rèn)為不妥的地方,當(dāng)事務(wù)提交失敗后沒有回滾而是直接transaction=null。

          由于ChannelBuilder的事務(wù)控制方法依賴于SessionHandler,Session只能在事務(wù)的開始獲取,在結(jié)束后銷毀。所以另外一個(gè)方法可用來判斷當(dāng)前事務(wù)是否正在進(jìn)行中。

          判斷事務(wù)是否進(jìn)行中
              /**
               * 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;
              }

          很明顯的,對(duì)于不是從SessionHandler處獲取的session,其transaction必定是null的。所以這個(gè)方法并不能用于構(gòu)造方法一的情況,其次就像API所講的,由于事務(wù)是可以嵌套的,所以這里返回false,不代表當(dāng)前Session就不是處在事務(wù)環(huán)境下,它可能是屬于別處的事務(wù)的。目前這個(gè)方法只能用于由ChannelBuilder創(chuàng)建的事務(wù)。

          當(dāng)事務(wù)開始后,我們可以開始獲取session對(duì)象了。

          獲取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();
              }

          如果對(duì)于第一種構(gòu)造方法這當(dāng)然是返回false,所以getSession只能用在beginTransaction和endTransaction的調(diào)用之間,否則會(huì)直接拋出異常。這強(qiáng)制我們必須為每次的數(shù)據(jù)庫操作開啟一個(gè)事務(wù)(顯式地調(diào)用beginTransaction)。

          ★Session的安全性

          下面我們回到最初的問題:ChannelBuilder和Session如何保證session的線程安全?

          我們也許還有一個(gè)疑問:SessionHandler中不是有一個(gè)curSession成員變量嗎?而且這個(gè)類還是一個(gè)單例模式,還作為ChannelBuilder的成員變量。那么不同的ChannelBuilder會(huì)不會(huì)錯(cuò)用了這個(gè)session呢?

          這個(gè)擔(dān)心是多余的,因?yàn)樵趕ession只在事務(wù)開始時(shí)才就被創(chuàng)建并賦予ChannelBuilder,此后ChannelBuilder會(huì)對(duì)session進(jìn)行緩存,后續(xù)的使用這個(gè)緩存session,而不是SessionHandler中的。

          其次,如果仔細(xì)觀察這個(gè)類,我們會(huì)發(fā)現(xiàn)ChannelBuilder這個(gè)類里面的session是只讀的,也就是它只在構(gòu)造方法被調(diào)用時(shí),或者事務(wù)開始時(shí)被設(shè)定,之后在事務(wù)運(yùn)行過程中它沒有對(duì)應(yīng)的setSession(Session anotherSession)方法來改變session。而且這個(gè)session是保存在ChannelBuilder中的私有成員變量,這意味著即便有不同的線程同時(shí)操作、訪問這個(gè)類的實(shí)例,他們所使用的session都是同一個(gè)的。

          在ChannelBuilder中提供了三個(gè)持久化的方法,分別用于持久化各種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");
                  }

              }

          請(qǐng)注意這里:如果采用構(gòu)造方法一,那么你將需要在執(zhí)行這些方法后手動(dòng)提交事務(wù)。如果你認(rèn)為你的操作不需要事務(wù)那么當(dāng)然可以不用。

          ★創(chuàng)建channel object

          在ChannelBuilder里面,數(shù)量最多的方法就是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對(duì)channel object的創(chuàng)建原則就是:
           A.如果能夠從持久層中找到對(duì)應(yīng)的記錄,那么從持久層返回
           B.如果找不到,則創(chuàng)建它并持久化它,然后返回該對(duì)象(已持久化)


          只要記得的一點(diǎn)就是:從ChannelBuilder返回的對(duì)象都是已經(jīng)持久化的。比如channel,此時(shí)我們甚至可以通過channel.getItems()來獲得其下屬的各個(gè)news item。


          -------------------------------------------------------------
          生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。
          posted on 2009-12-28 15:57 Paul Lin 閱讀(1242) 評(píng)論(0)  編輯  收藏 所屬分類: J2SE
          <2009年12月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(21)

          隨筆分類

          隨筆檔案

          BlogJava熱點(diǎn)博客

          好友博客

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 忻州市| 吉林省| 抚顺县| 黔江区| 崇左市| 赣榆县| 乌恰县| 呼玛县| 阳朔县| 甘泉县| 黔西县| 西昌市| 丹凤县| 阜平县| 永昌县| 洪泽县| 岳阳市| 宁都县| 七台河市| 阿尔山市| 枝江市| 昌乐县| 农安县| 西城区| 清远市| 辽中县| 普格县| 瓦房店市| 云阳县| 海宁市| 渝北区| 林甸县| 安顺市| 新源县| 多伦县| 象山县| 电白县| 崇州市| 锦屏县| 大足县| 东莞市|