spring+hibernate連接泄漏之殤

          Posted on 2012-11-07 16:32 terryxue 閱讀(2284) 評論(0)  編輯  收藏 所屬分類: java
          數據庫連接泄漏是件可怕的事情,可能直接導致系統停止響應,另外因事務管理錯誤而導致數據出現不一致也是件可怕的事情,在后臺系統中,這兩個問題經常會結伴出現,本文將通過案例詳解使用Spring+Hibernate時可能導致問題的幾種情況,希望對大家有所幫助。

          文章比較長,如果你是遇到了問題正急求解決方案的話,可以先只讀這一條:檢查你的dao是否有直接使用session,修改為使用hibernateTemplate。
          如果你希望更多的了解session, hibernateTemplate, transaction,請繼續。

          以下案例基于Struts2.3.1+Spring3.1.1+hibernate3.5.4完成,案例場景為對系統參數進行管理,涉及如下四個類:
          Action PropertyAction
          Service PropertyService/PropertyServiceImpl
          DAO PropertyDAO(繼承HibernateDaoSupport)
          Entity SysProperty

          1. Action中直接調用dao,未使用getHibernateTemplate
          在一個復雜的多層架構系統中,事務控制在service中完成是更合理的,不應交由view層來控制,本文假定你是這么做的。一般我們代碼的處理順序為action->service->dao,然而總會有人會打破這種邏輯,打破可以,但要注意如下問題。
          假定Action中有如下代碼:
          1 @Autowired
          2 private PropertyDAO propertyDAO;
          3 @Override
          4 public String execute() throws Exception {
          5      model = propertyDAO.get(1L);
          6      return null;
          7 }

           這里action直接訪問了dao中的get方法,因事務配置在service層,因此這里是沒有事務控制的。
          接下來我們看dao中的get方法,假定你的定義如下:
          1 public SysProperty get(Long id) {
          2     return (SysProperty) getSession().get(SysProperty.class, id);
          3 }

          代碼很簡單,getSession是父類HibernateDaoSupport中提供的方法,這里直接通過實體Id get得到結果。
          接下來發布一下系統,訪問頁面,好像一切OK,但是,再刷新幾下看看,可能你的連接池配置的比較大,要刷新多次,最后你發會現你的系統停止了響應。更直接點,調用你采用的連接池中的相關API檢測下當前連接占用情況,你會發現刷新一次,被占用連接增加一個,可怕的事情發生了。
          小結:在無事務管理器控制的情況下,通過getSession()打開的session不會被關閉,這個session所占用的數據庫連接也不會被釋放。

          接下來,來點更可怕的,假設你的action中是這樣的:
          1 private List<Long> ids;
          2 @Override
          3 public String execute() throws Exception {
          4   for(Long id: ids) {
          5     results.add(propertyDAO.get(id));
          6   }
          7   return null;
          8 }
          9 
          這時嘗試一次傳入多個id進行請求,請求結束后檢查下連接數,你發現傳入多少ID,就有多少連接被占用,也就是說每一次對dao的get調用都會占用一個不可釋放的連接。
          小結:如果沒有配置事務管理器且直接使用getSession得到session,每次getSession都會創建一個新的session,每個session占用一個新的數據庫連接,session無線程級別的共享。

          2. Action中直接調用dao,使用getHibernateTemplate
          當然,你可能沒遇到過前面的情況,因為你會在DAO中這樣寫代碼:
          1 public SysProperty get(Long id) {
          2     return getHibernateTemplate().get(getEntityClass(), id);
          3 }
          那么是否就一切OK呢?如果只是想解決連接泄漏這個問題的話,答案是Yes,嘗試多次請求,檢查下你的連接池狀況,沒有連接泄漏的情況出現。所以簡單而可靠的辦法就是不再直接使用getSession,而是使用getHibernateTemplate進行相應操作,當需要使用session時,可以用下面的方法:
          getHibernateTemplate().execute(new HibernateCallback<T>() {
            
          public T doInHibernate(Session session) throws HibernateException {
              Query queryObject 
          = session.createQuery(hql);
              //....
            }
          });
          But...如果你的系統真的出現了連接泄漏,可能你需要關注更多。
          還是前面Action中根據ID循環查詢的操作,在這個案例中,每一次Dao的get方法都將重復這樣的邏輯:創建session,分配session一個連接,關閉session,釋放session占用的連接,也就是說session不會在線程級別共享。讓我們繼續,進入第三種情況來進一步說明。

          3. Service未合理配置事務管理器
          絕大部分情況下我們會給service配置事務管理器,可能是通過xml或是@Transactional注解,但并非配置了就OK了。看下面的例子:
          @Transactional
          public
           class PropertyServiceImpl implements PropertyService {
              @Autowired
              
          private PropertyDAO propertyDAO;
              
              @PostConstruct
              
          public void init(){
                  propertyDAO.get(
          1L);
              }
              
          //
          }
          當然,假定你的DAO還是寫成了這樣:
          public SysProperty get(Long id) {
              
          return (SysProperty) getSession().get(SysProperty.class, id);
          }
          你期望在service初始化好后做一些數據庫操作,你也給service配置了事務管理器,接下來你啟動應用,然后檢查連接池中的連接數,你會發現有連接未被釋放!可能你會果斷的修改dao中的方法:
          public SysProperty get(Long id) {
              
          return getHibernateTemplate().get(getEntityClass(), id);
          }
          然后你會理所當然的認為,即使配置了事務管理器,依然不能使用getSession(),事實可能并非如此。
          我們調整一下代碼,不在init中調用dao中的get方法,改為如下:
          public SysProperty getProperty(Long id) {
              
          return propertyDAO.get(id);
          }
          然后DAO繼續使用(SysProperty) getSession().get(SysProperty.class, id),而action中的調用修改為:
          @Autowired
          private PropertyService propertyService;
          @Override
          public String execute() throws Exception {
              model 
          = propertyService.getProperty(1L);
              
          return null;
          }
          重新發布,調用,檢查連接數,發現無被占用連接存在。
          小結:如果正確配置了事務管理器,getSession是安全的。

          這時要清楚spring是通過AOP來實現事務控制的,而@PostConstruct方法不會受AOP控制,因此上面的init方法等于無事務管理器。
          那么再回頭來說,是否只要dao中使用getHibernateTemplate就不會有問題呢?
          假定service中的@PostConstruct方法如下:
          @PostConstruct
          public void init(){
              SysProperty p 
          = new SysProperty();
              p.setName(
          "test_property_1");
              propertyDAO.save(p);
          }
          強調一下,前面已經提到這個方法不受spring的事務管理器控制。
          假定DAO中的save方法如下:
          public void save(SysProperty o){
              getSession().save(o);
          }
          啟動一下應用,檢查一下連接數,再檢查一下數據是否有存儲到數據庫中。因為我們直接使用了getSession,因此連接不會釋放,這點前面已經提到,但同時我們還將發現數據沒有被存儲,當然這個也好理解,因為上面已經提到這個方法未配置事務管理器。
          小結:通過getSession().save()保存數據時,事務不會自動提交。

          現在再修改下DAO中的save方法:
          public void save(SysProperty o){
              getHibernateTemplate().save(o);
          }
          啟動一下應用,檢查一下連接數,再檢查一下數據是否有存儲到數據庫中。因為我們使用hibernateTemplate,因此連接有釋放,這點前面已經提到,但同時我們還發現數據也已存儲到數據庫中,說明hibernateTemplate會自動提交事務。
          小結:如果未配置事務管理器,通過hibernateTemplate操作時,會自動創建并提交事務。

          所以如果你覺得使用hibernateTemplate就OK了,那就要小心下面的代碼了:
          @PostConstruct
          public void init(){
            
          //1. 從你的賬號A中扣除一萬塊
            
          //2. 這里的代碼拋出了異常
            
          //3. 將你的賬號B中增加一萬塊 
          }
          如果上面的第2步出現了異常,那么因為1的事務已經提交,而3卻沒有執行,最終導致了數據的不一致,后果和連接泄漏一樣嚴重!
          除了@PostConstruct,還有其它原因會導致@Transactional無效,假定我們的service配置了事務管理器,但存在如下代碼:
          pubic void someServiceMethod(){
            
          new Thread(){
              
          public void run(){
                  doSomethingLater();
              }
            }
          }

          public void doSomethingLater(){
            
          //做一系列數據庫操作
          }
          那么你可以去驗證下doSomethingLater是否受事務管理器控制,事實上并不會,所以你需要理解spring AOP的機制,否則一個小坑會釀成災難。
          這里還有一種情況,你不是在類上面配置@Transactional,而是在方法上面配置,假定存在如下的代碼:
          //該方法不需要事務控制
          public void method1(){
              method2();
          }

          //下面的方法需要事務控制
          @Transactional
          public void method2(){
              
          //do something
          }
          因為你需要給不同的方法配置不同的事務機制,因此你沒有在類上面進行配置,然后你在客戶端進行了如下調用:service.method1(); method2中的方法會受事務管理嗎?可悲的是并不會。
          上面講到的線程調用和內部方法調用可以這樣來處理:
          @Autowired
          private ApplicationContext context;  
          public void method1(){
              PropertyService service 
          = context.getBean(PropertyService.class);
              service.method2();
          }
          小結:注意spring的AOP機制

          4. Service合理配置事務管理器
          最后補充一下,如果事務管理器配置正確的話會發生什么。這時不管你是用getSession還是getHibernateTemplate,結果都是一樣,session將在thread級別共享,session只有一個。

          總結:難得這周開始工作變得清閑,上班時間還能寫寫博客,想想前段日子真是自己何苦為難自己。回到話題,使用getSession沒什么大錯,因為你本應正確配置事務管理器。使用hibernateTemplate能解決所有連接泄漏的問題,但要小心他可能隱藏的事務問題。另外就是spring中內部方法調用時AOP的問題,創建新線程時的事務問題。最后希望這篇有點繞的文章能給你帶來幫助。

          posts - 9, comments - 24, trackbacks - 0, articles - 0

          Copyright © terryxue

          主站蜘蛛池模板: 荥经县| 温州市| 钦州市| 西乌| 宣化县| 壶关县| 饶河县| 沙田区| 新乡县| 太白县| 浏阳市| 广平县| 含山县| 丰都县| 吐鲁番市| 中阳县| 穆棱市| 广州市| 惠来县| 清流县| 兴业县| 堆龙德庆县| 黄龙县| 桂林市| 福建省| 琼海市| 灌云县| 玉门市| 平远县| 依兰县| 永福县| 墨江| 宁都县| 大关县| 滨州市| 遂宁市| 玛沁县| 龙胜| 西城区| 襄垣县| 静乐县|