SqlMapClient operation; uncategorized SQLException for SQL []; SQL state [null]; error code [0]; 异常解决

在使用Spring+Ibatis集成的时候,使用如下方式配置复杂类型的时候,总是报异常

 

< resultMap  class ="order"  id ="result" >
       
< result  property ="orderid"  column ="OrderId" />
       
< result  property ="customer"  column ="Customer" />
       
< result  property ="orderLines"  select ="getOrderLinesByOrder"  column ="OrderId" />
     
</ resultMap >

异常如下:

 

log4j:WARN No appenders could be found  for  logger (org.springframework.context.support.ClassPathXmlApplicationContext).
log4j:WARN Please initialize the log4j system properly.
Exception in thread 
" main "  org.springframework.jdbc.UncategorizedSQLException: SqlMapClient operation; uncategorized SQLException  for  SQL []; SQL state [ null ]; error code [ 0 ];   
---  The error occurred in ch10 / SpringAndIbatisOneToMany / Ibatis.xml.  
---  The error occurred  while  applying a result map.  
---  Check the result.  
---  Check the result mapping  for  the  ' orderLines '  property.  
---  Cause: java.lang.NullPointerException; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:   
---  The error occurred in ch10 / SpringAndIbatisOneToMany / Ibatis.xml.  
---  The error occurred  while  applying a result map.  
---  Check the result.  
---  Check the result mapping  for  the  ' orderLines '  property.  
---  Cause: java.lang.NullPointerException
Caused by: com.ibatis.common.jdbc.exception.NestedSQLException:   
---  The error occurred in ch10 / SpringAndIbatisOneToMany / Ibatis.xml.  
---  The error occurred  while  applying a result map.  
---  Check the result.  
---  Check the result mapping  for  the  ' orderLines '  property.  
---  Cause: java.lang.NullPointerException
    at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.executeQueryWithCallback(GeneralStatement.java:
188 )
    at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.executeQueryForObject(GeneralStatement.java:
104 )
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.queryForObject(SqlMapExecutorDelegate.java:
566 )
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.queryForObject(SqlMapExecutorDelegate.java:
541 )
    at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.queryForObject(SqlMapSessionImpl.java:
106 )
    at org.springframework.orm.ibatis.SqlMapClientTemplate$
1 .doInSqlMapClient(SqlMapClientTemplate.java: 243 )
    at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:
193 )
    at org.springframework.orm.ibatis.SqlMapClientTemplate.queryForObject(SqlMapClientTemplate.java:
241 )
    at ch10.SpringAndIbatisOneToMany.TestDAO.getOrderById(TestDAO.java:
11 )
    at ch10.SpringAndIbatisOneToMany.Test.main(Test.java:
22 )
Caused by: java.lang.NullPointerException
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.endTransaction(SqlMapExecutorDelegate.java:
782 )
    at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.endTransaction(SqlMapSessionImpl.java:
176 )
    at com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.endTransaction(SqlMapClientImpl.java:
154 )
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.autoEndTransaction(SqlMapExecutorDelegate.java:
883 )
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.queryForList(SqlMapExecutorDelegate.java:
622 )
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.queryForList(SqlMapExecutorDelegate.java:
589 )
    at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.queryForList(SqlMapSessionImpl.java:
118 )
    at com.ibatis.sqlmap.engine.impl.SqlMapClientImpl.queryForList(SqlMapClientImpl.java:
95 )
    at com.ibatis.sqlmap.engine.mapping.result.loader.ResultLoader.getResult(ResultLoader.java:
72 )
    at com.ibatis.sqlmap.engine.mapping.result.loader.ResultLoader.loadResult(ResultLoader.java:
59 )
    at com.ibatis.sqlmap.engine.mapping.result.BasicResultMap.getNestedSelectMappingValue(BasicResultMap.java:
502 )
    at com.ibatis.sqlmap.engine.mapping.result.BasicResultMap.getResults(BasicResultMap.java:
340 )
    at com.ibatis.sqlmap.engine.execution.SqlExecutor.handleResults(SqlExecutor.java:
381 )
    at com.ibatis.sqlmap.engine.execution.SqlExecutor.handleMultipleResults(SqlExecutor.java:
301 )
    at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeQuery(SqlExecutor.java:
190 )
    at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.sqlExecuteQuery(GeneralStatement.java:
205 )
    at com.ibatis.sqlmap.engine.mapping.statement.GeneralStatement.executeQueryWithCallback(GeneralStatement.java:
173 )
    ... 
9  more

 先说解决方式,主要是在Spring配置文件中,一定要为SqlMapClientFactoryBean配置DataSource属性,不能光为DAO配置dataSoure

 

bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  
<!--  此处应注入ibatis配置文件,而非sqlMap文件,否则会出现“there is no statement.....异常”  -->
  
< property  name ="configLocation" >
     
< value > ch10/SpringAndIbatisOneToMany/sqlMapConfig.xml </ value >
  
</ property >
  
< property  name ="dataSource" >
   
< ref  bean ="dataSource" />
 
</ property >

</ bean >

< bean  id ="testDAO"  class ="ch10.SpringAndIbatisOneToMany.TestDAO" >
  
< property  name ="dataSource" >
   
< ref  bean ="dataSource" />
   
</ property >
  
< property  name ="sqlMapClient" >
    
< ref  bean ="sqlMapClient" />
  
</ property >
</ bean >

 

源码分析如下:

     经代码跟踪发现问题发生在类org.springframework.orm.ibatis.SqlMapClientTemplate中的:  
   
  public   Object   execute(SqlMapClientCallback   action)   throws   DataAccessException   {  
  Assert.notNull(this.sqlMapClient,   "No   SqlMapClient   specified");  
   
  SqlMapSession   session   =   this.sqlMapClient.openSession();  
  Connection   ibatisCon   =   null;  
  try   {  
  if   (logger.isDebugEnabled())   {  
  logger.debug("Opened   SqlMapSession   ["   +   this.sqlMapClient   +   "]   for   iBATIS   operation");  
  }  
  Connection   springCon   =   null;  
  try   {  
                  ibatisCon   =   session.getCurrentConnection();  
  if   (ibatisCon   ==   null)   {  
  springCon   =   DataSourceUtils.getConnection(getDataSource());  
  session.setUserConnection(springCon);  
  if   (logger.isDebugEnabled())   {  
  logger.debug("Obtained   JDBC   Connection   ["   +   springCon   +   "]   for   iBATIS   operation");  
  }  
  }  
  else   {  
  if   (logger.isDebugEnabled())   {  
  logger.debug("Reusing   JDBC   Connection   ["   +   ibatisCon   +   "]   for   iBATIS   operation");  
  }  
  }  
                  return   action.doInSqlMapClient(session);  
  }  
  catch   (SQLException   ex)   {  
  throw   getExceptionTranslator().translate("SqlMapClient   operation",   null,   ex);  
  }  
  finally   {  
  DataSourceUtils.releaseConnection(springCon,   getDataSource());  
  }  
  }  
  finally   {  
  //   Only   close   SqlMapSession   if   we   know   we've   actually   opened   it  
  //   at   the   present   level.  
  if   (ibatisCon   ==   null)   {  
  session.close();  
  }  
  }  
  }  
   
  在这段代码里,spring从SqlMapSession得openSession()方法中得到了当前的session,   设定了  
  它的连接,   ibatis就用这个session得到了test与testDetail的内容,但在得到gossip的内容时,  
  ibatis使用了如下的方法得到session:  
  类com.ibatis.sqlmap.engine.impl.SqlMapClientImpl中:  
   
      public   Object   queryForObject(String   id,   Object   paramObject)   throws   SQLException   {  
          return   getLocalSqlMapSession().queryForObject(id,   paramObject);  
      }  
   
      protected   SqlMapSessionImpl   getLocalSqlMapSession()   {  
          SqlMapSessionImpl   sqlMapSession   =   (SqlMapSessionImpl)   localSqlMapSession.get();  
          if   (sqlMapSession   ==   null   ||   sqlMapSession.isClosed())   {  
              sqlMapSession   =   new   SqlMapSessionImpl(this);  
              localSqlMapSession.set(sqlMapSession);  
          }  
          return   sqlMapSession;  
      }  
       
  在这个方法中可以看出,对于SqlMapClientImpl来说,session应当会被保存在localSqlMapSession中,      
  在使用时再得到.   问题出现了,   spring也是从这个类得到session的,   但在得到session方法:  
      public   SqlMapSession   openSession()   {  
          SqlMapSessionImpl   sqlMapSession   =   new   SqlMapSessionImpl(this);  
          sqlMapSession.open();  
          return   sqlMapSession;  
      }  
  中,   这个session根本就没有保存进localSqlMapSession,   于是在得到gossip的内容时,   使用的是一个  
  新的session.   而这个新的session没有调用过setUserConnection,   其对应的transaction也就是初始值null,  
  而且ibatis在后面的  
  类com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate中  
   
      public   Object   queryForObject(SessionScope   session,   String   id,   Object   paramObject,   Object   resultObject)   throws   SQLException   {  
          Object   object   =   null;  
   
          MappedStatement   ms   =   getMappedStatement(id);  
          Transaction   trans   =   getTransaction(session);  
          boolean   autoStart   =   trans   ==   null;  
   
          try   {  
              trans   =   autoStartTransaction(session,   autoStart,   trans);  
   
              RequestScope   request   =   popRequest(session,   ms);  
              try   {  
                  object   =   ms.executeQueryForObject(request,   trans,   paramObject,   resultObject);  
              }   finally   {  
                  pushRequest(request);  
              }  
   
              autoCommitTransaction(session,   autoStart);  
          }   finally   {  
              autoEndTransaction(session,   autoStart);  
          }  
   
          return   object;  
      }  
   
      protected   Transaction   autoStartTransaction(SessionScope   session,   boolean   autoStart,   Transaction   trans)   throws   SQLException   {  
          Transaction   transaction   =   trans;  
          if   (autoStart)   {  
              session.getSqlMapTxMgr().startTransaction();  
              transaction   =   getTransaction(session);  
          }  
          return   transaction;  
      }  
  根本没有检查transaction是不是null,   于是就有上面的NullPointerException了.  
  于是将spring方法的代码改为:  
  public   Object   execute(SqlMapClientCallback   action)   throws   DataAccessException   {  
  Assert.notNull(this.sqlMapClient,   "No   SqlMapClient   specified");  
   
  SqlMapSession   session   =   this.sqlMapClient.openSession();  
  Connection   ibatisCon   =   null;  
  try   {  
  if   (logger.isDebugEnabled())   {  
  logger.debug("Opened   SqlMapSession   ["   +   this.sqlMapClient   +   "]   for   iBATIS   operation");  
  }  
  Connection   springCon   =   null;  
  try   {  
  ibatisCon   =   this.sqlMapClient.getCurrentConnection();  
  if   (ibatisCon   ==   null)   {  
  springCon   =   DataSourceUtils.getConnection(getDataSource());  
                      this.sqlMapClient.setUserConnection(springCon);  
  if   (logger.isDebugEnabled())   {  
  logger.debug("Obtained   JDBC   Connection   ["   +   springCon   +   "]   for   iBATIS   operation");  
  }  
  }  
  else   {  
  if   (logger.isDebugEnabled())   {  
  logger.debug("Reusing   JDBC   Connection   ["   +   ibatisCon   +   "]   for   iBATIS   operation");  
  }  
  }  
  return   action.doInSqlMapClient(this.sqlMapClient);  
  }  
  catch   (SQLException   ex)   {  
  throw   getExceptionTranslator().translate("SqlMapClient   operation",   null,   ex);  
  }  
  finally   {  
  DataSourceUtils.releaseConnection(springCon,   getDataSource());  
  }  
  }  
  finally   {  
  //   Only   close   SqlMapSession   if   we   know   we've   actually   opened   it  
  //   at   the   present   level.  
  if   (ibatisCon   ==   null)   {  
  // session.close();  
  }  
  }  
  }  
  一切就好了.  

        ibatis的openSession在上一版本时,每次打开的都是同一session,   因为它原来的代码在每次打开前会去localSqlMapSession中查找,如果没有才会打开个新的,而且会在返回前放入localSqlMapSession中,这被认为是一个BUG,所以在这个版本上改为返回一个新的session.而在这个session关闭时,才会把这个session返回给sessionPool中.  
          而在spring中,如楼上我的贴子所说,新打开的session没有进入localSqlMapSession,在打开一对多的表的子表时,   于是从sessionPool中得到一个没有被spring设置过setUserConnection的session,从而Transaction   trans   =   getTransaction(session);这里没有得到缺省的事务,那么   trans   =   autoStartTransaction(session,   autoStart,   trans);代码里就会去找这个session的txManager来启动一个事务,但txManager又是null,于是就会发出nullpoint异常了.  
          这时关键就是session的txManager是什么时候设置的.   我们可以看一下spring的SqlMapClientFactoryBean的代码就会发现,只有设置了它的dataSource属性时,才会去设置session的txManager,   所以这里就可以看出在一对多表时,SqlMapClientFactoryBean的dataSource属性一定要设置,   这样能保证在一对多时才不会出问题.

你可能感兴趣的:(spring,sql,object,ibatis,session,null)