Mybatis-Spring 小结

以下分析基于,Spring中使用Mybatis,并且配置事务

    <context:property-placeholder location="classpath:OracleJDBC.properties" />
    
    <!-- datasource -->
    <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
    p:driverClassName="${driver}"
    p:url="${url}"
    p:username="${username}"
    p:password="${password}"/>
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"
    p:dataSource-ref="datasource"/>
    
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
	    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
	    <property name="basePackage" value="com.daodao.mybatis.mapper"/>
    </bean>
    
    <bean id="testDaoService" class="com.daodao.mybatis.testDaoService"
    		p:stra="tttt"
    		p:intb="1111"/>
    		
     <!--事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"/>
    </bean>
    <!--声明事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>


spring中配置以下两种信息都可以用来生成mapper接口对应的实例:

org.mybatis.spring.mapper.MapperFactoryBean, 获取Mapper的Proxy

org.mybatis.spring.mapper.MapperScannerConfigurer 不用每次都给Mapper指定MapperFactoryBean


以MapperFactoryBean为例

1. 获取mapper对应的实例(JDK动态代理)

MapperFactoryBean
  @Override
  public T getObject() throws Exception {
	//这一步类似于SqlSessionTemplate.getMapper(Class<T> type)
    return getSqlSession().getMapper(this.mapperInterface);
  } 


补充:JDK动态代理具体实现:

MapperProxyFactory

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
mapperProxy为org.apache.ibatis.binding.MapperProxy

2.  mapper的方法调用 触发InvokeHandler:org.apache.ibatis.binding.MapperProxy

mapper的InvokeHandler为MapperProxy

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
a. final MapperMethod mapperMethod = cachedMapperMethod(method);

这里准备两个信息:

一个是crud的type信息+sql语句

一个是mapper被调用方法的输入参数+返回值信息

b. return mapperMethod.execute(sqlSession, args);
MapperMethod

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

简单来说会根据sql的type选择调用SqlSessionTemplate的不同方法

3. 以result = sqlSession.selectOne(command.getName(), param);为例其中sqlSession为SqlSessionTemplate但是所有的处理方法都是交给了属性sqlSessionProxy

  public <T> T selectOne(String statement, Object parameter) {
    return this.sqlSessionProxy.<T> selectOne(statement, parameter);
  }
sqlSessionProxy的InvokeHandler为 org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }

a.    SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator); 

这一步首先尝试从Thread.currentThread().threadLocals中获取sqlsession,获取成功则直接返回该sqlsession。获取失败则新建一个sqlsession。最后判断如果当前是处于transaction则放入Thread.currentThread().threadLocals,否则直接返回。

b.   Object result = method.invoke(sqlSession, args);

这一步调用DefaultSqlSession对应的方法

c. closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

这一步调用sqlsession.close(),有个小逻辑,如果当前处于spring 的transaction中则标记一下清除,等transaction结束的时候close(借transaction东风)。如果当前不处于transaction则直接close

  public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
    notNull(session, NO_SQL_SESSION_SPECIFIED);
    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    if ((holder != null) && (holder.getSqlSession() == session)) {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
      }
      holder.released();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
      }
      session.close();
    }
  }

总结:

准备阶段:
1. spring容器实例化mapper
BlogMapper mapper = SqlSessionTemplate.getMapper(BlogMapper.class);


2.custom代码的调用逻辑
Blog blog = mapper.selectBlog(101);


执行阶段:
3. mapper是个Proxy, MapperProxy中主要负责挑选调用SqlSessionTemplate的方法
if (SqlCommandType.INSERT == command.getType()) {

} else if (SqlCommandType.UPDATE == command.getType()) {
 
} else if (SqlCommandType.DELETE == command.getType()) {
 
} else if (SqlCommandType.SELECT == command.getType()) {


}


4. 以下是Mybatis正常的操作流程:

	session = DefaultSqlSessionFactory.openSession(false);
	try{
		操作
	} catch(Exception){
		session.commit();
	} finally{
		session.close();
	}


以下是Mybatis结合Spring之后,SqlSessionTemplate$SqlSessionInterceptor中流程:

总的来说就是如果存在Transaction则把sqlSession的开闭交给Spring Transaction去处理


//判断TransactionSynchronizationManager中有没有session
//1. 如果有则直接返回该session
//2. 否则执行DefaultSqlSessionFactory.openSession(false);。
		SqlSession sqlSession = getSqlSession();
		try {
			//调用DefaultSqlSession的对应crud方法(传入sql+参数)
			Object result = method.invoke(sqlSession, args);
//判断当前Thread有没有Spring的Transaction
//1. 如果没有则执行session.commit();
//2. 否则等待Spring Transaction结束的时候commit()
			if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
			  sqlSession.commit(true);
			}
			return result;
		} catch (Throwable t) {
//判断TransactionSynchronizationManager中有没有session
//1. 如果有则标记一下release,最后会在Spring Transaction结束的时候被清除。
//2. 否则执行session.close();
			closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
			throw t;
		} finally {
			closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
		}




SqlSessionTemplate 类似于JdbcTemplate,是能够保证每次操作sqlsession的打开和关闭。

DefaultSqlSession类似于jdbc的connection,需要手动的打开关闭。

注意:

如果当前Spring打开事务,虽然在运行时在Thread.currentThread().threadLocals会同时拥有以下两个,但是事实上只有一个connection,DefaultSqlSession实际操作的connection就是Spring事务一开始打开的connection

{org.apache.ibatis.session.defaults.DefaultSqlSessionFactory@67b37beb=org.mybatis.spring.SqlSessionHolder@f3e4c91, org.apache.commons.dbcp.BasicDataSource@6836d2c3=org.springframework.jdbc.datasource.ConnectionHolder@4314cb68}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值