hibernate clob保存

  费了比较多的精力终于解决了这个疑难问题,在百度上查阅了大量博客,论坛,一直没有放弃。通过自己的反复试验,像福尔摩斯抽丝剥茧一样终于找到问题的原因,确实很有必要记述下来,下面将解决该问题的来龙去脉细细道来。

            我们的网管平台的作业计划采集总是在运行了一段时间之后出现了java.sql.SQLException: 关闭的连接问题。异常堆栈如下:

  1. java.sql.SQLException: 关闭的连接  
  2.     at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapping.java:70 )  
  3.     at oracle.jdbc.driver.DatabaseError.newSQLException(DatabaseError.java:110 )  
  4.     at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:171 )  
  5.     at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:227 )  
  6.     at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:439 )  
  7.     at oracle.sql.CLOB.getDBAccess(CLOB.java:1083 )  
  8.     at oracle.sql.CLOB.getAsciiStream(CLOB.java:228 )  
  9.     at org.hibernate.lob.SerializableClob.getAsciiStream(SerializableClob.java:45 )  
  10.     at com.wri.hy.itmanagerv2.dg.dao.TaskParamDao.queryAllTaskParam(TaskParamDao.java:99 )  
  11.     at com.wri.hy.itmanagerv2.dg.autotask.dataservce.TaskParamService.queryAllTaskParam(TaskParamService.java:33 )  
  12.     at com.wri.hy.itmanagerv2.dg.autotask.sched.SchedController.init(SchedController.java:96 )  
  13.     at com.wri.hy.itmanagerv2.dg.autotask.sched.SchedReadDB.run(SchedReadDB.java:59 )  
  14.     at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)  
  15.     at java.util.concurrent.FutureTask$Sync.innerRunAndReset(Unknown Source)  
  16.     at java.util.concurrent.FutureTask.runAndReset(Unknown Source)  
  17.     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101 (Unknown Source)  
  18.     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(Unknown Source)  
  19.     at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)  
  20.     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)  
  21.     at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)  
  22.     at java.lang.Thread.run(Unknown Source)  

              查网上说“关闭的连接”是因为连接池中已经没有打开的Connection了,这个连接池是以前的同事仿造bonecp写的,应该不会有啥问题啊,我在连 接池代码里加了很多打印日志,还是没看出有什么问题来,过了很久我就把思路放在分析其它做数据库保存或者修改操作的代码,看是否在保存过程中的问题引发了 连接被关闭。发现在做采集数据保存时候出现了如下异常。

  1. org.springframework.dao.DataAccessResourceFailureException: Hibernate operation: Could not execute JDBC batch update; SQL [insert into ITMANAGERV2_PLANRESULTINFO (TASK_ID, RESULT, getTime, EXCEPTION_FLAG, EXCEPTION_LINE, ID) values (?, ?, ?, ?, ?, ?)]; Io 异 常: Software caused connection abort: socket write error; nested exception is java.sql.BatchUpdateException: Io 异 常: Software caused connection abort: socket write error  
  2.  Caused by: java.sql.BatchUpdateException: Io 异常: Software caused connection abort: socket write error  
  3.     at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:602 )  
  4.     at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:9350 )  
  5.     at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:210 )  
  6.     at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58 )  
  7.     at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:195 )  
  8.     at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:91 )  
  9.     at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:86 )  
  10.     at org.hibernate.jdbc.AbstractBatcher.prepareBatchStatement(AbstractBatcher.java:171 )  
  11.     at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2048 )  
  12.     at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2427 )  
  13.     at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:51 )  
  14.     at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:248 )  
  15.     at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:232 )  
  16.     at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:139 )  
  17.     at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297 )  
  18.     at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27 )  
  19.     at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985 )  
  20.     at org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:394 )  
  21.     at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:367 )  
  22.     at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdateAll(HibernateTemplate.java:688 )  
  23.     at com.wri.hy.itmanagerv2.dg.dao.ResultLogDao.saveCollections(ResultLogDao.java:13 )  
  24.     at com.wri.hy.itmanagerv2.dg.autotask.sched.ResultLogThread.run(ResultLogThread.java:157 )  
  25.     at java.lang.Thread.run(Unknown Source)  
  26.     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)  
  27.     at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)  
  28.     at java.lang.Thread.run(Unknown Source)  

              oh my god,一句简单的hibernateTemplate的saveOrUpdateAll居然会引起这个异常,在百度,谷歌上根据这个异常找来找去也找不 到一个说得有用的。只好老老实实开始分析存储代码,存储的POJO是这样定义的。

  1. public   class  PlanResultInfo {  
  2.    
  3.     private  Long id;             //id   
  4.       
  5.     private  PlanTaskInfo taskInfo;       //所属作业计划任务   
  6.       
  7.     private  Clob result;             //执行结果   
  8.       
  9.     private  String getTime;          //获取时间   
  10.       
  11.     private  String exception_flag;       //异常标识   
  12.       
  13.     private  String exception_line;       //异常行号   
  14.       
  15.     /**  
  16.      * 存储执行的结果   
  17.      */   
  18.     private   transient  String strResult;   
  19.    
  20.     public  String getException_flag() {  
  21.         return  exception_flag;  
  22.     }  
  23.    
  24.     public   void  setException_flag(String exception_flag) {  
  25.         this .exception_flag = exception_flag;  
  26.     }  
  27.    
  28.     public  String getException_line() {  
  29.         return  exception_line;  
  30.     }  
  31.    
  32.     public   void  setException_line(String exception_line) {  
  33.         this .exception_line = exception_line;  
  34.     }  
  35.    
  36.     public  String getGetTime() {  
  37.         return  getTime;  
  38.     }  
  39.    
  40.     public   void  setGetTime(String getTime) {  
  41.         this .getTime = getTime;  
  42.     }  
  43.    
  44.     public  Long getId() {  
  45.         return  id;  
  46.     }  
  47.    
  48.     public   void  setId(Long id) {  
  49.         this .id = id;  
  50.     }  
  51.    
  52.     public  PlanTaskInfo getTaskInfo() {  
  53.         return  taskInfo;  
  54.     }  
  55.    
  56.     public   void  setTaskInfo(PlanTaskInfo taskInfo) {  
  57.         this .taskInfo = taskInfo;  
  58.     }  
  59.    
  60.     public  Clob getResult() {  
  61.         return  result;  
  62.     }  
  63.    
  64.     public   void  setResult(Clob result) {  
  65.         this .result = result;  
  66.     }  
  67.    
  68.     public  String getStrResult() {  
  69.         return  strResult;  
  70.     }  
  71.    
  72.     public   void  setStrResult(String strResult) {  
  73.         this .strResult = strResult;  
  74.     }  
  75.       
  76.  }  

   其中result是个clob类型的字段,在hibernate的配置文件中result字段是这样定义的。

  1. <property name= "result"  type= "java.sql.Clob"  update= "true"  insert= "true" >  
  2.             <column name="RESULT" ></column>  
  3.         </property>  


            这样写感觉没问题啊,怎么会Software caused connection abort: socket write error,后来发现保存进clob的结果有时候String长度非常大,length都有几十万,算起来有100多K,这种情况下就会出现异常,但中小 量的数据是能够正常插入的。但是clob不是号称能保存4G的数据吗。我决定死马当活马医了,参照网上别的clob保存方式来重构代码。下面是第一版重构 代码:

  1. Session session =  null ;  
  2.         Transaction tx = null ;  
  3.         java.io.Writer writer = null ;  
  4.         Reader reader = null ;  
  5.         try  {  
  6.             session = this .getSessionFactory().openSession();  
  7.             planResultInfo.setResult(Hibernate.createClob(" " ));  
  8.             tx = session.beginTransaction();  
  9.             // 保存维护作业计划结果   
  10.             session.save(planResultInfo);  
  11.             session.flush();  
  12.             // 刷新   
  13.             session.refresh(planResultInfo, LockMode.UPGRADE);  
  14.                         tx.commit();  
  15.    
  16.             LOGGER.infoT("END TASK "  + planResultInfo.getTaskInfo().getId());} catch  (Exception e) {  
  17.             LOGGER.exception(e);  
  18.             // 回滚事务   
  19.             session.getTransaction().rollback();  
  20.         } finally  {  
  21.               
  22.             if  (session !=  null ) {  
  23.                 if  (session.isOpen()) {  
  24.                     // 关闭session   
  25.                     session.close();  
  26.                 }  
  27.             }  
  28.         }  


        很可惜,保存大的clob又出现了新鲜异常:

  1. org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update  
  2.     at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103 )  
  3.     at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91 )  
  4.     at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43 )  
  5.     at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:202 )  
  6.     at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235 )  
  7.     at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:139 )  
  8.     at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297 )  
  9.     at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27 )  
  10.     at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985 )  
  11.     at com.wri.hy.itmanagerv2.dg.dao.ResultLogDao.saveCollection(ResultLogDao.java:50 )  
  12.     at com.wri.hy.itmanagerv2.dg.autotask.sched.ResultLogThread.run(ResultLogThread.java:159 )  
  13.     at java.lang.Thread.run(Unknown Source)  
  14.     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)  
  15.     at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)  
  16.     at java.lang.Thread.run(Unknown Source)  
  17.  Caused by: java.sql.BatchUpdateException: 无法从套接字读取更多的数据  
  18.     at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:345 )  
  19.     at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10844 )  
  20.     at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58 )  
  21.     at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:195 )  
  22.     ... 11  more  

                   在网上查有关这个异常的问题也无所获,后来又是换ojdbc14.jar,也同样没作用。然后在网上看到有人在博客中写了一种写流的办法存clob,就出了重构第二版:

  1.              Session session =  null ;  
  2. Transaction tx = null ;  
  3. java.io.Writer writer = null ;  
  4.   
  5. try  {  
  6.     session = this .getSessionFactory().openSession();  
  7.     planResultInfo.setResult(Hibernate.createClob(" " ));  
  8.     tx = session.beginTransaction();  
  9.     // 保存维护作业计划结果   
  10.     session.save(planResultInfo);  
  11.     session.flush();  
  12.     // 刷新   
  13.     session.refresh(planResultInfo, LockMode.UPGRADE);  
  14.   
  15.     org.hibernate.lob.SerializableClob cb = (org.hibernate.lob.SerializableClob) planResultInfo  
  16.             .getResult();  
  17.     java.sql.Clob wrapClob = (java.sql.Clob) cb.getWrappedClob();  
  18.   
  19.     if  (wrapClob  instanceof  oracle.sql.CLOB) {  
  20.         oracle.sql.CLOB clob = (oracle.sql.CLOB) wrapClob;  
  21.         writer = new  BufferedWriter(clob.getCharacterOutputStream());  
  22.   
  23.         String contentStr = planResultInfo.getStrResult();  
  24.         LOGGER.infoT("result size : "  + contentStr.length());  
  25.         writer.write(contentStr);  
  26.                              writer.flush();  
  27.     }  
  28.   
  29.     // 保存维护作业计划日志   
  30.     session.save(planLogInfo);  
  31.   
  32.     tx.commit();  
  33.   
  34.     LOGGER.infoT("END TASK "  + planResultInfo.getTaskInfo().getId());  
  35. catch  (Exception e) {  
  36.     LOGGER.exception(e);  
  37.     // 回滚事务   
  38.     session.getTransaction().rollback();  
  39. finally  {  
  40.     try  {  
  41.           
  42.         if  ( null  != writer)  
  43.             writer.close();  
  44.     } catch  (IOException ioe) {  
  45.         LOGGER.exception(ioe);  
  46.     }  
  47.     if  (session !=  null ) {  
  48.         if  (session.isOpen()) {  
  49.             // 关闭session   
  50.             session.close();  
  51.         }  
  52.     }  
  53. }  

          这个可是人家博客上写的言之凿凿的啊,可是保存还是出现异常(这个和起初的保存出的异常一模一样)。

  1. java.io.IOException: Io 异常: Software caused connection abort: socket write error  
  2.     at oracle.jdbc.driver.DatabaseError.SQLToIOException(DatabaseError.java:519 )  
  3.     at oracle.jdbc.driver.OracleClobWriter.write(OracleClobWriter.java:122 )  
  4.     at java.io.Writer.write(Unknown Source)  
  5.     at java.io.Writer.write(Unknown Source)  
  6.     at com.wri.hy.itmanagerv2.dg.dao.ResultLogDao.saveCollection(ResultLogDao.java:64 )  
  7.     at com.wri.hy.itmanagerv2.dg.autotask.sched.ResultLogThread.run(ResultLogThread.java:159 )  
  8.     at java.lang.Thread.run(Unknown Source)  
  9.     at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)  
  10.     at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)  
  11.     at java.lang.Thread.run(Unknown Source)  


             这里很明显看到是java.io.Writer.write出现的异常,可能以前的博客作者没有保存大数据量的clob数据,这样会造成连接中断,最后一次重构终于成功,代码如下:

  1. Session session =  null ;  
  2.         Transaction tx = null ;  
  3.         java.io.Writer writer = null ;  
  4.         Reader reader = null ;  
  5.         try  {  
  6.             session = this .getSessionFactory().openSession();  
  7.             planResultInfo.setResult(Hibernate.createClob(" " ));  
  8.             tx = session.beginTransaction();  
  9.             // 保存维护作业计划结果   
  10.             session.save(planResultInfo);  
  11.             session.flush();  
  12.             // 刷新   
  13.             session.refresh(planResultInfo, LockMode.UPGRADE);  
  14.    
  15.             org.hibernate.lob.SerializableClob cb = (org.hibernate.lob.SerializableClob) planResultInfo  
  16.                     .getResult();  
  17.             java.sql.Clob wrapClob = (java.sql.Clob) cb.getWrappedClob();  
  18.    
  19.             if  (wrapClob  instanceof  oracle.sql.CLOB) {  
  20.                 oracle.sql.CLOB clob = (oracle.sql.CLOB) wrapClob;  
  21.                 writer = new  BufferedWriter(clob.getCharacterOutputStream());  
  22.    
  23.                 String contentStr = planResultInfo.getStrResult();  
  24.                 LOGGER.infoT("result size : "  + contentStr.length());  
  25.                 // // 截取前200000个字符,不然会出现异常   
  26.                 // if (contentStr.length() > 200000) {   
  27.                 // contentStr = contentStr.substring(0, 200000);   
  28.                 // }   
  29.    
  30.                 reader = new  BufferedReader( new  StringReader(contentStr));  
  31.                 char [] buffer =  new   char [ 1024 ];  
  32.    
  33.                 int  length;  
  34.                 while  ((length = reader.read(buffer)) >  0 ) {  
  35.                     writer.write(buffer, 0 , length);  
  36.                     writer.write(contentStr);  
  37.                     writer.flush();  
  38.                 }  
  39.    
  40.                 // writer.write(contentStr);   
  41.    
  42.                 // OutputStream outputStream = clob.getAsciiOutputStream();   
  43.                 //   
  44.                 // InputStream inputStream = new BufferedInputStream(   
  45.                 // new ByteArrayInputStream(contentStr.getBytes()));   
  46.                 // byte[] buff = new byte[10240];   
  47.                 //   
  48.                 // int len;   
  49.                 //   
  50.                 // while ((len = inputStream.read(buff)) > 0) {   
  51.                 // outputStream.write(buff, 0, len);   
  52.                 // outputStream.flush();   
  53.                 // }   
  54.                 // inputStream.close();   
  55.                 // outputStream.close();   
  56.    
  57.             }  
  58.    
  59.             // 保存维护作业计划日志   
  60.             session.save(planLogInfo);  
  61.    
  62.             tx.commit();  
  63.    
  64.             LOGGER.infoT("END TASK "  + planResultInfo.getTaskInfo().getId());  
  65.         } catch  (Exception e) {  
  66.             LOGGER.exception(e);  
  67.             // 回滚事务   
  68.             session.getTransaction().rollback();  
  69.         } finally  {  
  70.             try  {  
  71.                 if  ( null  != reader)  
  72.                     reader.close();  
  73.                 if  ( null  != writer)  
  74.                     writer.close();  
  75.             } catch  (IOException ioe) {  
  76.                 LOGGER.exception(ioe);  
  77.             }  
  78.             if  (session !=  null ) {  
  79.                 if  (session.isOpen()) {  
  80.                     // 关闭session   
  81.                     session.close();  
  82.                 }  
  83.             }  
  84.         }  

           这里要重点说明,对于大数据量的clob写入,必须用缓冲流循环写入字符数组,虽然执行时间长的,但可以执行成功,不会出现异常,

           总结:对于hibernate clob保存,如果clob中的数据量较小,普通saveorUpdate即可保存成功,但对于大数据量会导致连接断开,从而导致耗尽连接池中的连接,改 用流来写入,也同样不能直接写入全部String,也会引起connection abort,此时需要按缓冲流读写,降低连接过程中的io效率,这样就能保证插入大数据量的clob信息。

            这就是从解决java.sql.SQLException: 关闭的连接最终解决大数据量clob保存的流的保存方式的一个解决方案。希望能给其它解决clob保存的同仁一些参考。

猜你喜欢

转载自baggioback.iteye.com/blog/1693479