(0) Spring和MyBatis集成实例

通过MapperFactoryBean工厂类进行单个配置

1.创建Mapper接口有两种方式,可以通过注解@Mapper也可以通过XML配置文件实现

  • 通过注解@Mapper实现Dao接口
@Mapper
public interface UserMapper {
    @Select("select * from user where id=#{id}")
    public User getUser(int id);

    public void insertUser(User user);

    public void updateUserScore(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

</configuration>
  • 通过XML配置文件实现Dao接口
public interface UserMapper {
    public User getUser(int id);

    public void insertUser(User user);

    public void updateUserScore(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

    <mappers>
       <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="mybatis.mapping.UserMapper">

    <cache/>

    <select id="getUser" parameterType="int" resultType="mybatis.User" useCache="true">
        select * from user where id=#{id}
    </select>

    <insert id="insertUser" parameterType="mybatis.User">
        INSERT INTO user (name,password,score) VALUES (#{name},#{password},#{score})
    </insert>

    <update id="updateUserScore" parameterType="mybatis.User">
        UPDATE user set score = #{score} where id=#{id}
    </update>
</mapper>

2.创建Dao接口完成后,将spring的dataSource数据库连接注入到sqlSessionFactoryBean中生成sqlSession实例

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:/config.xml"/>
    <property name="dataSource" ref="dataSource"/>
</bean>

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

3.通过sqlSession获取Dao层Mapper实例有两种方法,一种是通过MapperFactoryBean创建单个Mapper实例,另一种是通过自动扫描,获取工程内所有的Mapper实例;

  • 将生成的SqlSessionFactory注入到MapperFactoryBean用于生成单个userMapper实例
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="mybatis.mapping.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
  • 将生成的SqlSessionFactory注入到MapperScannerConfigurer中,自动扫描工程获取全部Mapper实例
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName"  value="sqlSessionFactory"/>
    <property name="basePackage" value="mybatis.mapping"/>
</bean>

(1) Spring和MyBatis集成原理

1.通过MapperFactory进行的单个Mapper实例获取

  • 将Mapper接口和sqlSessionFactory注入到MapperFactoryBean中
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="mybatis.mapping.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
  • 通过MapperFactoryBean的getObject()方法获取Mapper实例,通过getMapper()方法实现
public T getObject() throws Exception {

    return getSqlSession().getMapper(this.mapperInterface);

  }

2.通过MapperScannerConfigurer进行Mapper实例扫描获取

在容器ApplicationContext实例化SqlSessionFactory

  • SqlSessionFactoryBean类实现了InitializingBean接口,所以会在SqlSessionFactoryBean工程类实例创建完成后执行afterPropertiesSet方法,在afterPropertiesSet方法中会执行buildSqlSessionFactory方法生成一个sqlSessionFactory对象,用于getObject()方法返回SqlSessionFactory;SqlSessionFactrory的创建是通过解析XML文件,同时注入Spring的DataSource创建Conguration实例;
public Set<BeanDefinitionHolder> doScan(String... basePackages) {

        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

          for (BeanDefinitionHolder holder : beanDefinitions) {

            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

            definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());

            //实际就是将扫描到的接口包装成MapperFactoryBean的实现类

            definition.setBeanClass(MapperFactoryBean.class);

            definition.getPropertyValues().add("addToConfig", this.addToConfig);

            boolean explicitFactoryUsed = false;

            //注入sqlSessionFactory对象,这个也很重要

            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {

              definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));

              explicitFactoryUsed = true;

            } else if (this.sqlSessionFactory != null) {

              definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);

              explicitFactoryUsed = true;

            }

            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {

              if (explicitFactoryUsed) {

                logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");

              }

              definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));

              explicitFactoryUsed = true;

            } else if (this.sqlSessionTemplate != null) {

              if (explicitFactoryUsed) {

                logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");

              }

              definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);

              explicitFactoryUsed = true;

            }



            if (!explicitFactoryUsed) {

              definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

            }

          }

        return beanDefinitions;

      }

在容器ApplicationContext实例化MapperScannerConfigurer扫描配置类

  • MapperScannerConfigurer对象的初始化过程,这个对象实现了BeanDefinitionRegistryPostProcessor接口,实现了该接口后在ApplicationContext初始化扫描applicationContext.xml配置文件注册BeanDefinition后执行postProcessBeanDefinitionRegistry方法
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if(this.processPropertyPlaceHolders) {
        this.processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
  • 在postProcessBeanDefinitionRegistry方法中初始化一个对象ClassPathMapperScanner,并讲执行scan—>doScan方法,扫描指定包下的所有Mapper类(@Mapper定义或者XML配置)注册到spring容器中,包装为MapperFactoryBean
public Set<BeanDefinitionHolder> doScan(String... basePackages) {

        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

          for (BeanDefinitionHolder holder : beanDefinitions) {

            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

            definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());

            //实际就是将扫描到的接口包装成MapperFactoryBean的实现类

            definition.setBeanClass(MapperFactoryBean.class);

            definition.getPropertyValues().add("addToConfig", this.addToConfig);

            boolean explicitFactoryUsed = false;

            //注入sqlSessionFactory对象,这个也很重要

            if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {

              definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));

              explicitFactoryUsed = true;

            } else if (this.sqlSessionFactory != null) {

              definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);

              explicitFactoryUsed = true;

            }

            if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {

              if (explicitFactoryUsed) {

                logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");

              }

              definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));

              explicitFactoryUsed = true;

            } else if (this.sqlSessionTemplate != null) {

              if (explicitFactoryUsed) {

                logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");

              }

              definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);

              explicitFactoryUsed = true;

            }



            if (!explicitFactoryUsed) {

              definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

            }

          }

        return beanDefinitions;

      }
  • 每一个Dao层Mapper类都在Spring容器内实例化一MapperFactoryBean,MapperFactoryBean继承自SqlSessionDaoSupport,需要通过setSqlSessionFactory()方法注入SqlSessionFactory用来创建会话SqlSessionTemplate类型的SqlSession
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if(!this.externalSqlSession) {
        this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
}
  • 每个Mapper类都有一个单例的SqlSessionTemplate类型的sqlSession用来管理线程不安全的SqlSession,底层通过ThreadLocal实现;SqlSessionTemplate执行具体的数据库操作是通过内部的代理类SqlSessionProxy实现
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sqlSessionFactory, "Property \'sqlSessionFactory\' is required");
    Assert.notNull(executorType, "Property \'executorType\' is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
  • SqlSessionProxy是通过JDK动态代理实现的类,在执行具体的数据库操作之前会尝试获取与该线程绑定的sqlSession,如果没有绑定则从新创建,如果已经绑定则直接使用;
private class SqlSessionInterceptor implements InvocationHandler {
    private SqlSessionInterceptor() {
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        Object unwrapped;
        try {
            Object t = method.invoke(sqlSession, args);
            if(!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }

            unwrapped = t;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            if(SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                DataAccessException translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                if(translated != null) {
                    unwrapped = translated;
                }
            }

            throw (Throwable)unwrapped;
        } finally {
            if(sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }

        }

        return unwrapped;
    }
}
  • 在动态代理类内如果Spring开启了事务则通过TransactionSynchronizationManager内部的ThreadLocal实现保证了非线程安全的sqlSession的线程安全实现;如果Spring未开启事务则TransactionSynchronizationManager内部为空,每次都会创建一个SqlSession实例,增删查改执行完成后进行事务提交;
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if(session != null) {
        return session;
    } else {
        if(LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }

        session = sessionFactory.openSession(executorType);
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}
  • 创建完成MapperFactoryBean后会在容器实例化时候调用getObject()方法进行实例化,对Mapper类会通过JDK动态代理生成实现类实例放入容器中;调用实现类实例相关方法时,会通过sqlSessionTemplate执行,会先获取到线程绑定的sqlSession后,再通过SqlSession执行相应的数据库操作;

3.MyBatis与Spring集成后的事务问题

MyBatis与Spring集成后默认将事务托管给Spring,如果Spring未开启事务管理则MyBatis也不会进行事务管理;MyBatis不进行事务的原理是每次增删查改数据库操作都创建一个sqlSession实例,数据库操作完成后会立即进行提交,这样就变成了无事务操作;

  • MyBatis与Spring集成后的事务问题主要变现在是否将线程绑定的Connection注入到TransactionSynchronizetionManager中
  • MyBatis与Spring集成并且Spring开启事务使用@Transaction,会在事务代理类中将线程绑定的Connection连接注入TransactionSynchronizetionManager中,根据TransactionSynchronizetionManager可以获取与线程绑定的sqlSession
  • MyBatis与Spring集成并且Spring未开启事务,则从TransactionSynchronizetionManager不能获取与线程绑定的sqlSession,每次执行数据库操作时都会通过sessionFactory.openSession()方法冲重新创建sqlSession;
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if(session != null) {
        return session;
    } else {
        if(LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }

        session = sessionFactory.openSession(executorType);
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

总结

  • MyBatis与Spring集成的难点在于将线程不安全的sqlSession转变成线程安全的,Spring的解决方案是通过对每个Mapper类创建线程安全的sqlSessionTemplate作为数据库操作的入口,任何通过sqlSessionTemplate执行数据库操作的线程都会尝试获取与线程绑定的sqlSession,因此每个线程执行多步数据库操作使用的是同一个sqlSession,这样就保证了线程安全;
  • MyBatis管理事务时,每个SqlSession绑定一个Executor,每个Executor绑定一个connection,当执行sqlSession.close()关闭事务的时,会调用connection.close()将数据库连接放回连接池;
  • SqlSession内绑定着一个Conection用来具体的数据库操作,connection数据库连接是有状态的,因此SqlSession是有状态的,线程不安全的
  • 在MyBatis与Spring集成中会为每个Mapper类创建一个sqlSessionTemplate实例,当多个线层对同一个Mapper类进行操作时,是通过同一个sqlSessionTemplate实例实现的,因此sqlSessionTemplate是线程安全的;LZ感觉其实可以整个上下文创建一个单例的sqlSessionTemplate实例再注入到每个Mapper类中,这样多个线程多个Mapper共有一个线程安全的sqlSessionTemplate,没有必要每个Mapper类创建一个;
  • 通过SqlSession执行数据库操作并且需要保证其线程安全的原因是为了事务操作,如果没有事务操作,直接通过connection进行数据库操作是最有效率的;
  • MyBatis与Spring集成后将事务操作完全托付给Spring,如果Spring未开启事务管理则默认每次增删查改操作都会创建一个新的sqlSession;
  • MyBatis与Spring集成后的整体逻辑过程是:
  • 在容器ApplicationContext实例化SqlSessionFactory,注入spring的dataSource实例
  • 在容器ApplicationContext注册MapperScannerConfigurer扫描配置类后会扫描特定包下的所有Mapper类,并将Mapper类包装成MapperFactoryBean注册到容器中(BeanDefinitionName);
  • 在创建MapperFactoryBean时会在其中创建会话SqlSessionTemplate类
  • 在容器applicationContext实例化时会通过getObject()方法创建Mapper类的JDK动态代理类的实例放入容器
  • 调用时注入Mapper类的JDK动态代理类的实例,调用其相关方法,在动态代理实现类中方法的执行是通过sqlSessionTemplate实现的;
  • sqlSessionTemplate执行数据库操作是通过代理类,首先获取线程绑定sqlSession,再通过这个sqlSession执行的;