Mybatis最重要的两个配置文件

mybatis-config.xml

<?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>
    <mappers>
        <package name="com.heoller.dao" />
    </mappers>
...
</configuration>

MapperXML.xml

<?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="com.heoller.dao.EmployeeDao">
    ...
</mapper>

最简单的示例程序

// 1.获取配置文件输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2.解析配置文件并返回SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取接口的动态代理对象
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
// 执行SQL查询
employeeDao.queryById(1);

从示例程序可以看出,要想执行SQL查询,需要先有SqlSessionFactory对象,然后通过SqlSessionFactory获取到sqlSession,最后获得接口的动态代理对象。
按照这个思路再来看Spring集成Mybatis时所做的工作就很清晰了。

Spring集成Mybatis

最重要的两个Bean

org.mybatis.spring.SqlSessionFactoryBean
  • 所属依赖包
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.0.2</version>
</dependency>
  • spring-beans.xml配置
<context:property-placeholder location="classpath:db.properties" />
<!-- 配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClass}" />
    <property name="url" value="${jdbc.jdbcUrl}" />
    <property name="username" value="${jdbc.user}" />
    <property name="password" value="${jdbc.password}" />
</bean>
<!-- 配置Mybatis SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 指定数据源 -->
    <property name="dataSource" ref="dataSource" />
    <!-- 指定Mapper配置文件位置 -->
    <property name="mapperLocations" value="classpath:mapper/*.xml" />
    <!-- 指定Mybatis全局配置文件 -->
    <property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>

SqlSessionFactoryBean类实现了InitializingBean接口,Spring在getBean过程中,实例化完对象之后执行AbstractAutowireCapableBeanFactory#invokeInitMethods,对于实现了InitializingBean接口的实例,会执行实例的afterPropertiesSet方法。

/**
 * {@inheritDoc}
 */
public void afterPropertiesSet() throws Exception {
    ...
    // buildSqlSessionFactory方法解析了mybatis的配置文件,并实例化了SqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
}
org.mybatis.spring.mapper.MapperScannerConfigurer
  • spring-beans.xml配置
<!-- 扫描Mybatis的Mapper接口并将动态代理对象放入Spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.heoller.dao" />
</bean>
  • 作用
    MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,是个Bean工厂的后置处理器。Spring在getBean之前会执行Bean工厂的后置处理器的postProcessBeanDefinitionRegistry方法(AbstractApplicationContext#invokeBeanFactoryPostProcessors)。
    /**
    * {@inheritDoc}
    */
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
      Scanner scanner = new Scanner(beanDefinitionRegistry);
      scanner.setResourceLoader(this.applicationContext);
      // 扫描Mapper接口
      scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
              ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
    // 内部Scanner类
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
      ...(省略分支代码)
          for (BeanDefinitionHolder holder : beanDefinitions) {
              GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
              ...
              // 加入MapperFactoryBean类的bean定义
              definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
              definition.setBeanClass(MapperFactoryBean.class);
              ...
          }
      }
      return beanDefinitions;
    }
    每个Mapper接口都会对应一个MapperFactoryBean类的bean定义,所以在每次注入Mapper接口时都会获得MapperProxy对象(单例),有了Mapper动态代理对象,我们就能执行Sql操作啦。
    public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
      public T getObject() throws Exception {
          return getSqlSession().getMapper(this.mapperInterface);
      }
    }
    // DefaultSqlSession.class
    public <T> T getMapper(Class<T> type) {
     return configuration.getMapper(type, this);
    }