【深刻浅出MyBatis系列十】与Spring集成

#0 系列目录#java

单独使用mybatis是有不少限制的(好比没法实现跨越多个session的事务),并且不少业务系统原本就是使用spring来管理的事务,所以mybatis最好与spring集成起来使用。 #1 Spring集成配置#web

<bean id="sqlSessionFactory"   
    class="org.mybatis.spring.SqlSessionFactoryBean">  
    <property name="dataSource" ref="datasource"></property>  
    <property name="configLocation" value="classpath:context/mybatis-config.xml"></property>  
    <!-- 
    mapperLocations:经过正则表达式,支持mybatis动态扫描添加mapper不用像ibatis,用一个还要蛋疼滴添加一个include
    -->
    <property name="mapperLocations" value="classpath*:/com/tx/demo/**/*SqlMap.xml" />
    <!-- 
    typeHandlersPackage: 因为mybatis默认入参若是为空,又没有指定jdbcType时会抛出异常,在这里经过配置一些默认的类型空值插入的handle,以便处理mybatis的默认类型为空的状况。
    例如NullAbleStringTypeHandle经过实现当String字符串中为null是调用ps.setString(i,null)其余经常使用类型雷同。
    -->  
    <property name="typeHandlersPackage" value="com.tx.core.mybatis.handler"></property>
    <!-- 
    failFast:开启后将在启动时检查设定的parameterMap,resultMap是否存在,是否合法。我的建议设置为true,这样能够尽快定位解决问题。否则在调用过程当中发现错误,会影响问题定位。
    --> 
    <property name="failFast" value="true"></property>  
    <property name="plugins">  
        <array>  
            <bean class="com.tx.core.mybatis.interceptor.PagedDiclectStatementHandlerInterceptor">  
                <property name="dialect">  
                    <bean class="org.hibernate.dialect.PostgreSQLDialect"></bean>  
                </property>  
            </bean>  
        </array>  
    </property>  
</bean>  
<!--
myBatisExceptionTranslator:用以支持spring的异常转换,经过配置该translator能够将mybatis异常转换为spring中定义的DataAccessException。
-->
<bean id="myBatisExceptionTranslator" class="org.mybatis.spring.MyBatisExceptionTranslator">  
    <property name="dataSource">  
        <ref bean="datasource"></ref>  
    </property>  
</bean>  
  
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">  
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>  
    <constructor-arg name="executorType" ref="SIMPLE"></constructor-arg>  
    <constructor-arg name="exceptionTranslator" ref="myBatisExceptionTranslator"></constructor-arg>  
</bean>  
  
<bean id="myBatisDaoSupport" class="com.tx.core.mybatis.support.MyBatisDaoSupport">  
    <property name="sqlSessionTemplate">  
        <ref bean="sqlSessionTemplate"/>  
    </property>  
</bean>

#2 Spring事务配置#正则表达式

<!-- 自动扫描业务包 -->  
<context:component-scan base-package="com.xxx.service" />  
  
<!-- 数据源 -->  
<jee:jndi-lookup id="jndiDataSource" jndi-name="java:comp/env/jdbc/datasource" />
  
<!-- 配置事务 -->  
<bean id="txManager"  
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
    <property name="dataSource" ref="jndiDataSource" />  
</bean>  
<!-- 配置基于注解的事务aop -->  
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>

或:spring

<!-- 自动扫描业务包 -->  
<context:component-scan base-package="com.xxx.service" />  
  
<!-- 数据源 -->  
<jee:jndi-lookup id="jndiDataSource" jndi-name="java:comp/env/jdbc/datasource" />

<!-- 配置事务管理器,注意这里的dataSource和SqlSessionFactoryBean的dataSource要一致,否则事务就没有做用了 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
 
<!-- 配置事务的传播特性 -->
<bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributes">
        <props>
            <prop key="add*">PROPAGATION_REQUIRED</prop>
            <prop key="edit*">PROPAGATION_REQUIRED</prop>
            <prop key="remove*">PROPAGATION_REQUIRED</prop>
            <prop key="insert*">PROPAGATION_REQUIRED</prop>
            <prop key="update*">PROPAGATION_REQUIRED</prop>
            <prop key="del*">PROPAGATION_REQUIRED</prop>
            <prop key="*">readOnly</prop>
        </props>
    </property>
</bean>

<!--把事务控制在Service层-->
<aop:config>
    <aop:pointcut id="pc" expression="execution(public * com.jeasy..service.*.*(..))" />
    <aop:advisor pointcut-ref="pc" advice-ref="baseTransactionProxy" />
</aop:config>

#3 单个集成#sql

<!-- 集成mybatis -->  
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
    <property name="dataSource" ref="jndiDataSource" />  
    <property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />  
    <!-- 自动配置别名 -->  
    <property name="typeAliasesPackage" value="com.xxx.dto" />  
</bean>  
  
<!--建立dao bean(只需提供接口不需提供实现类 )-->  
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">  
    <property name="mapperInterface" value="com.xxx.dao.UserDao" />  
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
</bean>

咱们不但要明白如何使用,更要明白为何要这么使用。数据库

SqlSessionFactoryBean是一个工厂bean,它的做用就是解析配置(数据源、别名等)express

MapperFactoryBean是一个工厂bean,在spring容器里,工厂bean是有特殊用途的,当spring将工厂bean注入到其余bean里时,它不是注入工厂bean自己而是调用bean的getObject方法。咱们接下来就看看这个getObjec方法干了些什么:编程

public T getObject() throws Exception {  
  return getSqlSession().getMapper(this.mapperInterface);  
}

看到这里你们应该就很明白了,这个方法和咱们以前单独使用Mybatis的方式是同样的,都是先获取一个Sqlsession对象,而后再从Sqlsession里获取Mapper对象(再次强调Mapper是一个代理对象,它代理的是mapperInterface接口,而这个接口是用户提供的dao接口)。天然,最终注入到业务层就是这个Mapper对象。数组

实际的项目通常来讲不止一个Dao,若是你有多个Dao那就按照上面的配置依次配置便可。缓存

#4 如何使用批量更新# 前一节讲了如何注入一个mapper对象到业务层, mapper的行为依赖于配置,mybatis默认使用单个更新(即ExecutorType默认为SIMPLE而不是BATCH),固然咱们能够经过修改mybatis配置文件来修改默认行为,但若是咱们只想让某个或某几个mapper使用批量更新就不得行了。这个时候咱们就须要使用模板技术

<!--经过模板定制mybatis的行为 -->  
<bean id="sqlSessionTemplateSimple" class="org.mybatis.spring.SqlSessionTemplate">     
    <constructor-arg index="0" ref="sqlSessionFactory" />  
    <!--更新采用单个模式 -->  
    <constructor-arg index="1" value="SIMPLE"/>  
</bean>  
      
<!--经过模板定制mybatis的行为 -->  
<bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">     
    <constructor-arg index="0" ref="sqlSessionFactory" />  
    <!--更新采用批量模式 -->  
    <constructor-arg index="1" value="BATCH"/>  
</bean>

这里笔者定义了两个模板对象,一个使用单个更新,一个使用批量更新。有了模板以后咱们就能够改变mapper的行为方式了

<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">  
    <property name="mapperInterface" value="com.xxx.dao.UserDao" />  
    <property name="sqlSessionTemplate" ref="sqlSessionTemplateBatch" />  
</bean>

跟上一节的mapper配置不一样的是,这里不须要配置sqlSessionFactory属性,只须要配置sqlSessionTemplate(sqlSessionFactory属性在模板里已经配置好了)

因为在3.1.1升级后,可直接经过BatchExcutor实现具体的批量执行。在该excutor中会重用上一次相同的PreparedStatement

#5 经过自动扫描简化mapper的配置# 前面的章节能够看到,咱们的dao须要一个一个的配置在配置文件中,若是有不少个dao的话配置文件就会很是大,这样管理起来就会比较痛苦。幸亏mybatis团队也意识到了这点,他们利用spring提供的自动扫描功能封装了一个自动扫描dao的工具类,这样咱们就可使用这个功能简化配置:

<!-- 采用自动扫描方式建立mapper bean(单个更新模式) -->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="com.xxx.dao" />  
    <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplateSimple" />  
    <property name="markerInterface" value="com.xxx.dao.SimpleDao" />  
</bean>  
       
<!-- 采用自动扫描方式建立mapper bean(批量更新模式) -->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="com.xxx.dao" />  
    <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplateBatch" />  
    <property name="markerInterface" value="com.xxx.dao.BatchDao" />  
</bean>

MapperScannerConfigurer自己涉及的spring的技术我就很少讲了,感兴趣且对spring原理比较了解的能够去看下它的源码。咱们重点看一下它的三个属性:

basePackage:扫描器开始扫描的基础包名,支持嵌套扫描;

sqlSessionTemplateBeanName:前文提到的模板bean的名称;

markerInterface:基于接口的过滤器,实现了该接口的dao才会被扫描器扫描,与basePackage是与的做用。

除了使用接口过滤外,还可以使用注解过滤

<!-- 采用自动扫描方式建立mapper bean(批量更新模式) -->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <property name="basePackage" value="com.xxx.dao" />  
    <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplateBatch" />  
    <property name="annotationClass" value="com.xxx.dao.BatchAnnotation" />  
</bean>

annotationClass:配置了该注解的dao才会被扫描器扫描,与basePackage是与的做用。须要注意的是,与上个接口过滤条件只能配一个。

markerInterface:markerInterface是用于指定一个接口的,当指定了markerInterface以后,MapperScannerConfigurer将只注册继承自markerInterface的接口。

#6 与Spring集成源码分析#

##6.1 SqlSessionFactory## 咱们知道在Mybatis的全部操做都是基于一个SqlSession的,而SqlSession是由SqlSessionFactory来产生的,SqlSessionFactory又是由SqlSessionFactoryBuilder来生成的。可是Mybatis-Spring是基于SqlSessionFactoryBean的。在使用Mybatis-Spring的时候,咱们也须要SqlSession,并且这个SqlSession是内嵌在程序中的,通常不须要咱们直接访问。SqlSession也是由SqlSessionFactory来产生的,可是Mybatis-Spring给咱们封装了一个SqlSessionFactoryBean,在这个bean里面仍是经过SqlSessionFactoryBuilder来创建对应的SqlSessionFactory,进而获取到对应的SqlSession。经过SqlSessionFactoryBean咱们能够经过对其指定一些属性来提供Mybatis的一些配置信息。因此接下来咱们须要在Spring的applicationContext配置文件中定义一个SqlSessionFactoryBean。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
       <!-- dataSource属性是必须指定的,它表示用于链接数据库的数据源 -->  
       <property name="dataSource" ref="dataSource" />  
       <property name="mapperLocations"  
              value="classpath:com/tiantian/ckeditor/mybatis/mappers/*Mapper.xml" />  
       <property name="typeAliasesPackage" value="com.tiantian.ckeditor.model" />  
</bean>
  • mapperLocations:它表示咱们的Mapper文件存放的位置,当咱们的Mapper文件跟对应的Mapper接口处于同一位置的时候能够不用指定该属性的值。

  • configLocation:用于指定Mybatis的配置文件位置。若是指定了该属性,那么会以该配置文件的内容做为配置信息构建对应的SqlSessionFactoryBuilder,可是后续属性指定的内容会覆盖该配置文件里面指定的对应内容。

  • typeAliasesPackage:它通常对应咱们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名做为包括包名的别名。多个package之间能够用逗号或者分号等来进行分隔。

  • typeAliases:数组类型,用来指定别名的。指定了这个属性后,Mybatis会把这个类型的短名称做为这个类型的别名,前提是该类上没有标注@Alias注解,不然将使用该注解对应的值做为此种类型的别名。

<property name="typeAliases">  
   <array>  
       <value>com.tiantian.mybatis.model.Blog</value>  
       <value>com.tiantian.mybatis.model.Comment</value>  
   </array>  
</property>
  • plugins:数组类型,用来指定Mybatis的Interceptor。

  • typeHandlersPackage:用来指定TypeHandler所在的包,若是指定了该属性,SqlSessionFactoryBean会自动把该包下面的类注册为对应的TypeHandler。多个package之间能够用逗号或者分号等来进行分隔。

  • typeHandlers:数组类型,表示TypeHandler。

接下来就是在Spring的applicationContext文件中定义咱们想要的Mapper对象对应的MapperFactoryBean了。经过MapperFactoryBean能够获取到咱们想要的Mapper对象。MapperFactoryBean实现了Spring的FactoryBean接口,因此MapperFactoryBean是经过FactoryBean接口中定义的getObject方法来获取对应的Mapper对象的。在定义一个MapperFactoryBean的时候有两个属性须要咱们注入,一个是Mybatis-Spring用来生成实现了SqlSession接口的SqlSessionTemplate对象的sqlSessionFactory;`另外一个就是咱们所要返回的对应的Mapper接口了。

定义好相应Mapper接口对应的MapperFactoryBean以后,咱们就能够把咱们对应的Mapper接口注入到由Spring管理的bean对象中了,好比Service bean对象。这样当咱们须要使用到相应的Mapper接口时,MapperFactoryBean会从它的getObject方法中获取对应的Mapper接口,而getObject内部仍是经过咱们注入的属性调用SqlSession接口的getMapper(Mapper接口)方法来返回对应的Mapper接口的。这样就经过把SqlSessionFactory和相应的Mapper接口交给Spring管理实现了Mybatis跟Spring的整合。

若是想使用MapperScannerConfigurer,想要了解该类的做用,就得先了解MapperFactoryBean。

##6.2 MapperFactoryBean## MapperFactoryBean的出现为了代替手工使用SqlSessionDaoSupport或SqlSessionTemplate编写数据访问对象(DAO)的代码,使用动态代理实现

好比下面这个官方文档中的配置:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

org.mybatis.spring.sample.mapper.UserMapper是一个接口,咱们建立一个MapperFactoryBean实例,而后注入这个接口和sqlSessionFactory(mybatis中提供的SqlSessionFactory接口,MapperFactoryBean会使用SqlSessionFactory建立SqlSession)这两个属性。

以后想使用这个UserMapper接口的话,直接经过spring注入这个bean,而后就能够直接使用了,spring内部会建立一个这个接口的动态代理

当发现要使用多个MapperFactoryBean的时候,一个一个定义确定很是麻烦,因而mybatis-spring提供了MapperScannerConfigurer这个类,它将会查找类路径下的映射器并自动将它们建立成MapperFactoryBean

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
    <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate" />  
</bean>

这段配置会扫描org.mybatis.spring.sample.mapper下的全部接口,而后建立各自接口的动态代理类。

##6.3 MapperScannerConfigurer##

若是咱们须要使用MapperScannerConfigurer来帮咱们自动扫描和注册Mapper接口的话咱们须要在Spring的applicationContext配置文件中定义一个MapperScannerConfigurer对应的bean。对于MapperScannerConfigurer而言有一个属性是咱们必须指定的,那就是basePackage。basePackage是用来指定Mapper接口文件所在的基包的,在这个基包或其全部子包下面的Mapper接口都将被搜索到。多个基包之间可使用逗号或者分号进行分隔。最简单的MapperScannerConfigurer定义就是只指定一个basePackage属性,如:

package org.format.dynamicproxy.mybatis.dao;
public interface UserDao {
    public User getById(int id);
    public int add(User user);    
    public int update(User user);    
    public int delete(User user);    
    public List<User> getAll();    
}

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--dataSource属性指定要用到的链接池-->
    <property name="dataSource" ref="dataSource"/>
    <!--configLocation属性指定mybatis的核心配置文件-->
    <property name="configLocation" value="classpath:sqlMapConfig.xml"/>
    <property name="mapperLocations" value="classpath:sqlMapper/*Mapper.xml" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="org.format.dynamicproxy.mybatis.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

有时候咱们指定的基包下面的并不全是咱们定义的Mapper接口,为此MapperScannerConfigurer还为咱们提供了另外两个能够缩小搜索和注册范围的属性。一个是annotationClass,另外一个是markerInterface

  • annotationClass:当指定了annotationClass的时候,MapperScannerConfigurer将只注册使用了annotationClass注解标记的接口。
  • markerInterface:markerInterface是用于指定一个接口的,当指定了markerInterface以后,MapperScannerConfigurer将只注册继承自markerInterface的接口。

若是上述两个属性都指定了的话,那么MapperScannerConfigurer将取它们的并集,而不是交集。即便用了annotationClass进行标记或者继承自markerInterface的接口都将被注册为一个MapperFactoryBean。

  • sqlSessionFactory:这个属性已经废弃。当咱们使用了多个数据源的时候咱们就须要经过sqlSessionFactory来指定在注册MapperFactoryBean的时候须要使用的SqlSessionFactory,由于在没有指定sqlSessionFactory的时候,会以Autowired的方式自动注入一个。换言之当咱们只使用一个数据源的时候,即只定义了一个SqlSessionFactory的时候咱们就能够不给MapperScannerConfigurer指定SqlSessionFactory

  • sqlSessionFactoryBeanName:它的功能跟sqlSessionFactory是同样的,只是它指定的是定义好的SqlSessionFactory对应的bean名称。

  • sqlSessionTemplate:这个属性已经废弃。它的功能也是至关于sqlSessionFactory的,由于就像前面说的那样,MapperFactoryBean最终仍是使用的SqlSession的getMapper方法取的对应的Mapper对象。当定义有多个SqlSessionTemplate的时候才须要指定它。对于一个MapperFactoryBean来讲SqlSessionFactory和SqlSessionTemplate只须要其中一个就能够了,当二者都指定了的时候,SqlSessionFactory会被忽略

  • sqlSessionTemplateBeanName:指定须要使用的sqlSessionTemplate对应的bean名称。

注意:因为使用sqlSessionFactory和sqlSessionTemplate属性时会使一些内容在PropertyPlaceholderConfigurer以前加载,致使在配置文件中使用到的外部属性信息没法被及时替换而出错,所以官方如今新的Mybatis-Spring中已经把sqlSessionFactory和sqlSessionTemplate属性废弃了,推荐你们使用sqlSessionFactoryBeanName属性和sqlSessionTemplateBeanName属性。

咱们先经过测试用例debug查看userDao的实现类究竟是什么:

输入图片说明

咱们能够看到,userDao是1个MapperProxy类的实例。看下MapperProxy的源码,没错,实现了InvocationHandler,说明使用了jdk自带的动态代理:

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

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

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor接口是一个能够修改spring工厂中已定义的bean的接口,该接口有个postProcessBeanDefinitionRegistry方法

输入图片说明

而后咱们看下ClassPathMapperScanner中的关键是如何扫描对应package下的接口的。

输入图片说明

其实MapperScannerConfigurer的做用也就是将对应的接口的类型改造为MapperFactoryBean,而这个MapperFactoryBean的属性mapperInterface是原类型。MapperFactoryBean本文开头已分析过。

因此最终咱们仍是要分析MapperFactoryBean的实现原理!

MapperFactoryBean继承了SqlSessionDaoSupport类,SqlSessionDaoSupport类继承DaoSupport抽象类,DaoSupport抽象类实现了InitializingBean接口,所以实例个MapperFactoryBean的时候,都会调用InitializingBean接口的afterPropertiesSet方法。

DaoSupport的afterPropertiesSet方法:

输入图片说明

MapperFactoryBean重写了checkDaoConfig方法:

输入图片说明

而后经过spring工厂拿对应的bean的时候:

输入图片说明

这里的SqlSession是SqlSessionTemplate,SqlSessionTemplate的getMapper方法:

输入图片说明

Configuration的getMapper方法,会使用MapperRegistry的getMapper方法:

输入图片说明

MapperRegistry的getMapper方法:

输入图片说明

MapperProxyFactory构造MapperProxy:

输入图片说明

**没错! MapperProxyFactory就是使用了jdk组带的Proxy完成动态代理。**MapperProxy原本一开始已经提到。MapperProxy内部使用了MapperMethod类完成方法的调用:

输入图片说明

下面,咱们以UserDao的getById方法来debug看看MapperMethod的execute方法是如何走的:

@Test
public void testGet() {
    int id = 1;
    System.out.println(userDao.getById(id));
}
<select id="getById" parameterType="int" resultType="org.format.dynamicproxy.mybatis.bean.User">
    SELECT * FROM users WHERE id = #{id}
</select>

输入图片说明

输入图片说明

##6.4 SqlSessionTemplate##

Mybatis-Spring为咱们提供了一个实现了SqlSession接口的SqlSessionTemplate类,它是线程安全的,能够被多个Dao同时使用。同时它还跟Spring的事务进行了关联,确保当前被使用的SqlSession是一个已经和Spring的事务进行绑定了的。并且它还能够本身管理Session的提交和关闭。当使用了Spring的事务管理机制后,SqlSession还能够跟着Spring的事务一块儿提交和回滚。

使用SqlSessionTemplate时咱们能够在Spring的applicationContext配置文件中以下定义:

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

经过源码咱们何以看到 SqlSessionTemplate 实现了SqlSession接口,也就是说咱们可使用SqlSessionTemplate 来代理以往的DefailtSqlSession完成对数据库的操做,可是DefailtSqlSession这个类不是线程安全的,因此这个类不能够被设置成单例模式的。

若是是常规开发模式 咱们每次在使用DefailtSqlSession的时候都从SqlSessionFactory当中获取一个就能够了。可是与Spring集成之后,Spring提供了一个全局惟一的SqlSessionTemplate示例 来完成DefailtSqlSession的功能,问题就是:不管是多个dao使用一个SqlSessionTemplate,仍是一个dao使用一个SqlSessionTemplate,SqlSessionTemplate都是对应一个sqlSession,当多个web线程调用同一个dao时,它们使用的是同一个SqlSessionTemplate,也就是同一个SqlSession,那么它是如何确保线程安全的呢?让咱们一块儿来分析一下。

  1. 首先,经过以下代码建立代理类,表示建立SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,若是调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
     PersistenceExceptionTranslator exceptionTranslator) {

   notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
   notNull(executorType, "Property 'executorType' is required");

   this.sqlSessionFactory = sqlSessionFactory;
   this.executorType = executorType;
   this.exceptionTranslator = exceptionTranslator;
   this.sqlSessionProxy = (SqlSession) newProxyInstance(
       SqlSessionFactory.class.getClassLoader(),
       new Class[] { SqlSession.class },
       new SqlSessionInterceptor());
}
  1. 核心代码就在 SqlSessionInterceptor的invoke方法当中:
private class SqlSessionInterceptor implements InvocationHandler {
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //获取SqlSession(这个SqlSession才是真正使用的,它不是线程安全的)
     //这个方法能够根据Spring的事务上下文来获取事务范围内的sqlSession
     //一会咱们在分析这个方法
     final SqlSession sqlSession = getSqlSession(
         SqlSessionTemplate.this.sqlSessionFactory,
         SqlSessionTemplate.this.executorType,
         SqlSessionTemplate.this.exceptionTranslator);
     try {
       //调用真实SqlSession的方法
       Object result = method.invoke(sqlSession, args);
       //而后判断一下当前的sqlSession是否被Spring托管 若是未被Spring托管则自动commit
       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) {
         Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
         if (translated != null) {
           unwrapped = translated;
         }
       }
       throw unwrapped;
     } finally {
       //关闭sqlSession
       //它会根据当前的sqlSession是否在Spring的事务上下文当中来执行具体的关闭动做
       //若是sqlSession被Spring管理 则调用holder.released(); 使计数器-1
       //不然才真正的关闭sqlSession
       closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
     }
   }
}
  1. 在上面的invoke方法当中使用了俩个工具方法 分别是:

SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)

SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)

那么这个俩个方法又是如何与Spring的事务进行关联的呢?

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {     
   //根据sqlSessionFactory从当前线程对应的资源map中获取SqlSessionHolder,当sqlSessionFactory建立了sqlSession,就会在事务管理器中添加一对映射:key为sqlSessionFactory,value为SqlSessionHolder,该类保存sqlSession及执行方式 
   SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
   //若是holder不为空,且和当前事务同步 
   if (holder != null && holder.isSynchronizedWithTransaction()) { 
     //hodler保存的执行类型和获取SqlSession的执行类型不一致,就会抛出异常,也就是说在同一个事务中,执行类型不能变化,缘由就是同一个事务中同一个sqlSessionFactory建立的sqlSession会被重用 
     if (holder.getExecutorType() != executorType) { 
       throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); 
     } 
     //增长该holder,也就是同一事务中同一个sqlSessionFactory建立的惟一sqlSession,其引用数增长,被使用的次数增长 
     holder.requested(); 
     //返回sqlSession 
     return holder.getSqlSession(); 
   } 
   //若是找不到,则根据执行类型构造一个新的sqlSession 
   SqlSession session = sessionFactory.openSession(executorType); 
   //判断同步是否激活,只要SpringTX被激活,就是true 
   if (isSynchronizationActive()) { 
     //加载环境变量,判断注册的事务管理器是不是SpringManagedTransaction,也就是Spring管理事务 
     Environment environment = sessionFactory.getConfiguration().getEnvironment(); 
     if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 
       //若是是,则将sqlSession加载进事务管理的本地线程缓存中 
       holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 
       //以sessionFactory为key,hodler为value,加入到TransactionSynchronizationManager管理的本地缓存ThreadLocal<Map<Object, Object>> resources中 
       bindResource(sessionFactory, holder); 
       //将holder, sessionFactory的同步加入本地线程缓存中ThreadLocal<Set<TransactionSynchronization>> synchronizations 
       registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 
       //设置当前holder和当前事务同步 
       holder.setSynchronizedWithTransaction(true); 
       //增长引用数 
       holder.requested(); 
     } else { 
       if (getResource(environment.getDataSource()) == null) { 
       } else { 
         throw new TransientDataAccessResourceException( 
             "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 
       } 
     } 
   } else { 
   } 
   return session; 
}
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 
   //其实下面就是判断session是否被Spring事务管理,若是管理就会获得holder  
   SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
   if ((holder != null) && (holder.getSqlSession() == session)) { 
     //这里释放的做用,不是关闭,只是减小一下引用数,由于后面可能会被复用 
     holder.released(); 
   } else { 
     //若是不是被spring管理,那么就不会被Spring去关闭回收,就须要本身close 
     session.close(); 
   } 
}

这样咱们就能够经过Spring的依赖注入在Dao中直接使用SqlSessionTemplate来编程了,这个时候咱们的Dao多是这个样子:

package com.tiantian.mybatis.dao;
 
import java.util.List;
import javax.annotation.Resource;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
import com.tiantian.mybatis.model.Blog;
 
@Repository
public class BlogDaoImpl implements BlogDao {
 
    private SqlSessionTemplate sqlSessionTemplate;
 
    public void deleteBlog(int id) {
       sqlSessionTemplate.delete("com.tiantian.mybatis.mapper.BlogMapper.deleteBlog", id);
    }
 
    public Blog find(int id) {
      return sqlSessionTemplate.selectOne("com.tiantian.mybatis.mapper.BlogMapper.selectBlog", id);
    }
 
    public List<Blog> find() {
       return this.sqlSessionTemplate.selectList("com.tiantian.mybatis.mapper.BlogMapper.selectAll");
    }
 
    public void insertBlog(Blog blog) {
       this.sqlSessionTemplate.insert("com.tiantian.mybatis.mapper.BlogMapper.insertBlog", blog);
    }
 
    public void updateBlog(Blog blog) {
       this.sqlSessionTemplate.update("com.tiantian.mybatis.mapper.BlogMapper.updateBlog", blog);
    }
   
    public SqlSessionTemplate getSqlSessionTemplate() {
       return sqlSessionTemplate;
    }
   
    @Resource
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
       this.sqlSessionTemplate = sqlSessionTemplate;
    }
}
相关文章
相关标签/搜索