# MyBatis-Spring 的整合

mybatis 和 spring 的整合需要先注入 SqlSessionFactoryBean 和 MapperScannerConfigurer, 那么这两个类的作用是什么呢?

# MyBatis 的执行流程

1
2
3
4
5
6
7
8
9
10
void test() {
InputStream inputStream = Resources.getResourceAsStream("classpath:mybatis-config.xml");
SqlSessionFactory sqlsessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = SqlsessionFactory.openSession();
//方式一
session.selectOne("xxx.UserMapper.selectById", "101");
//方式二
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(101);
}
  1. 通过 SqlSessionFactoryBuilder 将读取到的 mybatis-config.xml 配置文件生成 SqlSessionFactory
  2. 通过 SqlSessionFactory 生成 SqlSession 对象
  3. 通过 SqlSession 的方法进行数据库请求 / 通过 SqlSession 获取动态代理 Mapper 对象
  4. 通过 Mapper 进行数据库操作
  • SqlSessionFactoryBuilder :

    用于创建 SQLSessionFactory 对象的类,我推测应该就是建造者模式,创建完之后就没有用处了,所以应该为局部方法变量

  • SqlSessionFactory :

    一个应用程序中最好只有一个,即单例

  • SqlSession :

    线程不安全,所以不能被共享,每个线程都应该有它自己的 SqlSession 实例

# MyBatis-Spring : 将 MyBatis 代码无缝整合到 Spring

MyBaits-Spring 会将 MyBatis 代码无缝的整合到 Spring 中,他将允许 MyBats 参与到 Spring 的事务管理中,创建映射器 mapper 和 SqlSession 并注入到 Bean 中

在 Spring 项目中应用了 MyBatis 都会有下面的两个 Bean 配置,这两个配置就是实现 xml 加载,mapper 和 SqlSession 注入的起始配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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

<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="xxx.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>


# SqlSessionFactoryBean : 加载 xml 及 build SqlSessionFactory 对象

我们可以从上面的配置看出,SqlSessionFactoryBean 配置了数据源,mapper 的 xml 路径以及 configuration 的 xml 路径,所以其内部一定实现了解析 xml 配置文件和 SqlSessionFactory 对象的创建. SqlSessionFactoryBean 继承关系图如下 :

MapperScannerConfigurer继承关系图形

FactoryBean 子类都是通过 getObject () 方法来获取到实际的 Bean 对象的,在 SqlSessionFactoryBean 中也就是获取 SqlSessionFactory, 而创建 SqlSessionFactory 势必需要一个 Configuration 类 (MyBatis 的核心类之一,主要用来存放读取到的 xml 配置,包括 mapper.xml)

关键类及方法 :

  • XMLConfigBuilder : 通过调用其 parse () 方法来解析 mybatis-config.xml 配置,如果有 mapper.xml, 其会通过 XMLMapperBuilder 进行解析加载,并将解析的数据赋值到 Configuration
  • XMLMapperBuilder : 通过调用其 parse () 方法来解析 mapper.xml 配置,并将解析的数据赋值到 Configuration
  • 将放油解析数据的 Configuration 作为 SqlSessionFactoryBuilder.builder () 的参数,创建 SqlSessionFactory 对象

# MapperScannerConfigurer : 扫描 Mapper 接口路径,将 Mapper 偷梁换柱成 MaooerFactoryBean

MapperScannerConfigurer 是 MyBatis-Spring 项目中为了实现方便加载 Mapper 接口,以及将 Mapper 偷梁换柱成 MaooerFactoryBean (在 Spring 中是无法直接将接口注册成 Bean 的,接口无法实例化,而 Spring 在创建 Bean 的过程中会调用构造函数)

查看继承关系图 :

img

整个方法分为三部分 :

  1. 调用父类 ClassPathBeanDefinitionScanner 的 doScan () 方法加载路径下所有 mapper 生成对应的 BeanDefinition
  2. 通过 definnition.setBeanClass (MapperFactoryBean.class) 偷梁换柱成 MapperFactoryBean
  3. 通过 definition.getPropertyValues ().add () 添加 MapperFacotryBean 所需的字段或者方法参数信息 : sqlSessionFactory, mapperInterface 等

# 加载 Configuration

Configuration 包含了会深深影响 MyBatis 行为的设置和属性信息。配置文档的顶层结构如下

  • configuration
    • properties (属性)
    • settings (设置)
    • typeAliases (类型别名)
    • typeHandlers (类型处理器)
    • objectFactory (对象工厂)
    • plugins (插件)
    • environments (环境配置)
      • environment (环境变量)
      • transactionManager (事务管理器)
      • dataSource (数据源)
    • databaseIdProvider (数据库厂商标识)
    • mapper (映射器)

其中我们最常配置的是 settings. 一个配置相对完整的 settings 元素的示例如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

<settings>
<!-- 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存 -->
<setting name="cacheEnabled" value="true"/>
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否允许单一语句返回多结果集(需要驱动支持) -->
<setting name="multipleResultSetsEnabled" value="true"/>
<!-- 使用列标签代替列名 -->
<setting name="useColumnLabel" value="true"/>
<!-- 允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby) -->
<setting name="useGeneratedKeys" value="false"/>
<!-- 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)-->
<setting name="autoMappingBehavior" value="PARTIAL"/>
<!-- 指定发现自动映射目标未知列(或者未知属性类型)的行为。
NONE: 不做任何反应
WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARN)
FAILING: 映射失败 (抛出 SqlSessionException) -->
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<!-- 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新 -->
<setting name="defaultExecutorType" value="SIMPLE"/>
<!-- 设置超时时间,它决定驱动等待数据库响应的秒数 -->
<setting name="defaultStatementTimeout" value="25"/>
<!-- 为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖 -->
<setting name="defaultFetchSize" value="100"/>
<!-- 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false-->
<setting name="safeRowBoundsEnabled" value="false"/>
<!-- 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射 -->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据-->
<setting name="localCacheScope" value="SESSION"/>
<!-- 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER-->
<setting name="jdbcTypeForNull" value="OTHER"/>
<!-- 指定哪个对象的方法触发一次延迟加载-->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<!-- 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。-->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

img

# Mapper 代理类的生成

# 加载 Mapper 接口

加载 mapper 接口有两种形式:一种是根据设置的 package 找到路径下面所有的 class 并通过 configuration.addMapper () 加载。另一种是根据设置的 Mapper 接口路径直接通过 configuration.addMapper () 加载 class. 所以加载 Mapper 接口最终都是 configuration.addMapper () 来实现的

而针对 MyBatis-Spring 项目,则是获取 MapperScannerConfigurer 的 basePackage 参数,并通过 ClassPathMapperScanner 扫描到设置的 basePackage 路径下的所有 class, 并得到 BeanDefinition, , 最终是得到了 MapperFactoryBean

  1. Configuration 维护了一个 MapperRegistry 对象,该对象的主要作用就是加载 Mapper 接口和获取 MapperProxy
  2. MapperRegistry 维护了一个 key 为 mapper 接口 class 对象,value 为 MapperProxyFactory 的 map
  3. MapperProxy 是通过 MapperProxyFactory 创建的
  4. MapperProxy 实现 Mapper 接口方法是委托 MapperMethod 执行的
  5. MapperMethod 执行接口方法时通过 SqlCommand 来判断要执行的具体 SQL 节点,并最终委托 SqlSession 执行
  6. SqlCommand 内部的信息是通过 MapperStatement 中获取的