表test中有两个字段与testdetail表的id关联,配置如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap>
<typeAlias type="com.apress.prospring.ch12.domain.Test"
alias="test" /> <typeAlias type="com.apress.prospring.ch12.domain.TestDetail"
alias="detail" /> <resultMap class="test" id="result">
<result property="id" column="ID" />
<result property="name" column="NAME" />
<result property="runDate" column="RUNDATE" />
</resultMap> <resultMap class="detail" id="gossipResult">
<result property="id" column="ID" />
<result property="data" column="DATA" />
</resultMap> <resultMap class="test" id="testDetail" extends="result">
<result property="detail.id" column="DETAILID" />
<result property="detail.data" column="DETAILDATA" />
<result property="gossip" select="getGossipById"
column="GOSSIP" />
</resultMap> <select id="getDetailById" resultMap="testDetail"
parameterClass="int">
SELECT C.ID, C.NAME, C.RUNDATE, C.DETAILID, CD.DATA AS
DETAILDATA, C.GOSSIP FROM TEST C, TESTDETAIL CD WHERE
C.DETAILID = CD.ID and C.ID = #value#
</select> <select id="getGossipById" resultMap="gossipResult"
parameterClass="String">
SELECT * FROM TESTDETAIL WHERE ID=#value#
</select></sqlMap>
而spring的配置如下.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans 
PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd"><beans>
<bean id="listBasedObscenityFilter"
class="com.apress.prospring.business.ListBasedObscenityFilter">
<property name="obscenities">
<list>
<value>crap</value>
</list>
</property>
</bean>
<bean id="obscenityFilterAdvisor"
class="com.apress.prospring.business.aop.ObscenityFilterAdvice">
<property name="filter" ref="listBasedObscenityFilter">
</property>
</bean> <bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:oci8:@newsoft"></property>
<property name="username" value="cs"></property>
<property name="password" value="cs"></property>
</bean> <bean id="sqlMapClient"
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="SqlMapConfig.xml"></property>
</bean> <bean id="testDao"
class="com.apress.prospring.ch12.data.SqlMapClientTestDao">
<property name="dataSource" ref="dataSource"></property>
<property name="sqlMapClient" ref="sqlMapClient"></property>
</bean>
</beans>
而对应的dao类中的代码为:
public class SqlMapClientTestDao extends SqlMapClientDaoSupport
    implements
      TestDao {  public Test getById(int id) {
    return (Test) getSqlMapClientTemplate().queryForObject("getDetailById",
        new Integer(id));
  }
}

解决方案 »

  1.   

    经代码跟踪发现问题发生在类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();
    }
    }
    }
    一切就好了.
    说了这么多,其实就是想请教各种高手,真的是有这个BUG,还是我的配置或代码写错了
      

  2.   

    所以才奇怪啊,我就是照书上写的来作,结果就出现这个问题了.我当时还以为SPRING自带的ibatis版本不对,但我单独下的ibatis也是一样,不会是我下的spring版本有问题吧?
      

  3.   

    在又花了一天的时间进行查找之后,终于明白是什么回事了.
        原来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属性一定要设置, 这样能保证在一对多时才不会出问题.