1 Spring框架整合Mybatis示例
1.1 创建演示项目

1.2 项目目录结构

1.3 依赖配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kkarma</groupId>
<artifactId>spring-mybatis-app</artifactId>
<version>1.0.0</version>
<name>spring-mybatis-app</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
1.4 项目配置文件
1.4.1 数据库连接配置文件
druid.driver=com.mysql.cj.jdbc.Driver
druid.url=jdbc:mysql://10.10.3.95:3306/sys-library?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone\
=Asia/Shanghai
druid.userName=adsion
druid.password=MTIzNDU2
# 初始化连接数
druid.pool.init=3
# 高峰期过后,保留连接吃的个数
druid.pool.minIdle=5
# 高峰期,最大能创建连接的个数
druid.pool.MaxActive=20
# 等待的时间
durid.pool.timeout=60
1.4.2 Mybatis框架全局配置文件
<?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>
<!-- 二级缓存开启, 默认为true -->
<setting name="cacheEnabled" value="true" />
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
</configuration>
1.4.3 Spring框架配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:Context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 通过配置文件方式注册bean -->
<bean id="myBook01" class="com.kkarma.pojo.LibBook" >
<property name="bookId" value="6"></property>
<property name="bookIndexNo" value="xxooooxx"></property>
<property name="bookName" value="java高级程序设计"></property>
<property name="bookAuthor" value="张三"></property>
<property name="bookPublisher" value="清华大学出版社"></property>
<property name="bookCateId" value="1"></property>
<property name="bookStock" value="2"></property>
</bean>
<!--声明使用注解配置-->
<Context:annotation-config />
<!--声明Spring工厂注解的扫描范围-->
<Context:component-scan base-package="com.kkarma"/>
<!--引用外部文件-->
<Context:property-placeholder location="db.properties"/>
<!--配置DruidDataSources-->
<bean id="DruidDataSources" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${druid.driver}"/>
<property name="url" value="${druid.url}"/>
<property name="username" value="${druid.userName}"/>
<property name="password" value="${druid.password}"/>
<property name="initialSize" value="${druid.pool.init}"/>
<property name="minIdle" value="${druid.pool.minIdle}"/>
<property name="maxActive" value="${druid.pool.MaxActive}"/>
<property name="maxWait" value="${durid.pool.timeout}"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--配置数据源-->
<property name="dataSource" ref="DruidDataSources"/>
<!--配置mapper的路径-->
<property name="mapperLocations" value="classpath*:mappers/**/*Mapper.xml">
</property>
<!--配置需要定义别名的实体类的包-->
<property name="typeAliasesPackage" value="com.kkarma.pojo"/>
<!--配置需要mybatis的主配置文件-->
<property name="configLocation" value="mybatis-config.xml"/>
</bean>
<!-- 配置MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
<!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>-->
<property name="basePackage" value="com.kkarma.mapper"/>
</bean>
<!-- 将spring事务管理配置给spring -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="DruidDataSources"/>
</bean>
<!-- 通过Spring jdbc提供的<tx>标签声明事物的管理策略,并给事务设置隔离级别以及传播机制 -->
<tx:advice id="MyAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="Insert*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
<tx:method name="Update*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
<tx:method name="Delete*" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
<tx:method name="Query*" isolation="REPEATABLE_READ" propagation="SUPPORTS"/>
</tx:attributes>
</tx:advice>
<!--将事务管理以Aop配置,应用于ServiceI方法(ServiceImp)-->
<aop:config>
<aop:pointcut id="MyManager" expression="execution(* com.kkarma.service.*.*(..))"/>
<aop:advisor advice-ref="MyAdvice" pointcut-ref="MyManager" />
</aop:config>
</beans>
1.4.4 **Mapper.xml文件

1.4.5 Mapper接口
package com.kkarma.mapper;
import com.kkarma.pojo.LibBook;
import java.util.List;
/**
* @Author: karma
* @Date: 2023/3/17 0017 - 03 - 17 - 10:18
* @Description: com.kkarma.mapper
* @version: 1.0
*/
public interface LibBookMapper {
List<LibBook> selectAllBook();
}
1.4.6 实体类
package com.kkarma.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* @Author: karma
* @Date: 2023/3/17 0017 - 03 - 17 - 10:09
* @Description: com.kkarma.pojo
* @version: 1.0
*/
@Data
@NoArgsConstructor
@ToString
public class LibBook implements Serializable {
private Long bookId;
private String bookIndexNo;
private String bookName;
private String bookAuthor;
private String bookDescription;
private String bookPublisher;
private Integer bookCateId;
private Integer bookStock;
}
1.5 测试
package com.kkarma;
import static org.junit.Assert.assertTrue;
import com.kkarma.mapper.LibBookMapper;
import com.kkarma.pojo.LibBook;
import com.kkarma.service.ILibBookService;
import lombok.AllArgsConstructor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
/**
* Unit test for simple App.
*/
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
@RunWith(value = SpringJUnit4ClassRunner.class)
public class AppTest
{
@Autowired
// private ILibBookService bookService;
private LibBookMapper bookMapper;
/**
* Rigorous Test :-)
*/
@Test
public void queryBooksTest()
{
List<LibBook> libBooks = bookMapper.selectAllBook();
libBooks.forEach( System.out::println);
}
}

2 Spring框架整合Mybatis原理分析
看看, 之前如果我们单独只用Mybatis框架的时候, 测试代码是怎么写的
1) 创建一个SqlSessionFactoryBuilder对象:
SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
2) 创建一个SqlSessionFactory对象:
SqlSessionFactory factory = factoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
3) 创建一个SqlSession对象:
SqlSession sqlSession = factory.openSession();
4) 获取Mapper层指定接口的动态代理对象:
LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);
5) 调用接口方法获取返回结果即可:
List<LibBook> list = mapper.selectAllBook();
对比一下spring框架集成Mybatis框架之后
public class AppTest
{
@Autowired
private LibBookMapper bookMapper;
@Test
public void queryBooksTest()
{
List<LibBook> libBooks = bookMapper.selectAllBook();
}
}
对比一下,发现是不是很多操作就被简化了, 整个SqlSession对象的创建过程和Mapper动态代理对象的创建过程都没有了, 那么mybatis-spring到底是怎么实现这个操作的简化的呢, 下面我们一起来分析一下这个过程。
2.1 SqlSessionFactoryBean
首先我们看看Spring的配置文件applicationContext.xml, 其中定义了sqlSessionFactory的bean声明, 
创建的对象是SqlSessionFactoryBean对象,前面已经详细说过Mybatis框架的核心内容了,我们应该都知道在Mybatis中我们都是通过SqlSessionFactoryBuilder创建SqlSessionFactory对象,然后再通过SqlSessionFactory对象创建SqlSession对象,从而获取数据库连接等等操作。
见名知意,SqlSessionFactoryBean对象很定也是跟创建SqlSessionFactory紧密相关的。到源码里面去看看
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(SqlSessionFactoryBean.class);
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
/** 在创建SqlSessionFactoryBean的时候,已经创建了sqlSessionFactoryBuilder对象 */
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
// EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName();
private boolean failFast;
private Interceptor[] plugins;
private TypeHandler<?>[] typeHandlers;
private String typeHandlersPackage;
@SuppressWarnings("rawtypes")
private Class<? extends TypeHandler> defaultEnumTypeHandler;
private Class<?>[] typeAliases;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private LanguageDriver[] scriptingLanguageDrivers;
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
// issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider;
private Class<? extends VFS> vfs;
private Cache cache;
private ObjectFactory objectFactory;
private ObjectWrapperFactory objectWrapperFactory;
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
this.objectWrapperFactory = objectWrapperFactory;
}
public DatabaseIdProvider getDatabaseIdProvider() {
return databaseIdProvider;
}
public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
this.databaseIdProvider = databaseIdProvider;
}
public Class<? extends VFS> getVfs() {
return this.vfs;
}
public void setVfs(Class<? extends VFS> vfs) {
this.vfs = vfs;
}
public Cache getCache() {
return this.cache;
}
public void setCache(Cache cache) {
this.cache = cache;
}
public void setPlugins(Interceptor... plugins) {
this.plugins = plugins;
}
public void setTypeAliasesPackage(String typeAliasesPackage) {
this.typeAliasesPackage = typeAliasesPackage;
}
public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
this.typeAliasesSuperType = typeAliasesSuperType;
}
public void setTypeHandlersPackage(String typeHandlersPackage) {
this.typeHandlersPackage = typeHandlersPackage;
}
public void setTypeHandlers(TypeHandler<?>... typeHandlers) {
this.typeHandlers = typeHandlers;
}
public void setDefaultEnumTypeHandler(
@SuppressWarnings("rawtypes") Class<? extends TypeHandler> defaultEnumTypeHandler) {
this.defaultEnumTypeHandler = defaultEnumTypeHandler;
}
public void setTypeAliases(Class<?>... typeAliases) {
this.typeAliases = typeAliases;
}
public void setFailFast(boolean failFast) {
this.failFast = failFast;
}
public void setConfigLocation(Resource configLocation) {
this.configLocation = configLocation;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
public void setMapperLocations(Resource... mapperLocations) {
this.mapperLocations = mapperLocations;
}
public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
this.configurationProperties = sqlSessionFactoryProperties;
}
public void setDataSource(DataSource dataSource) {
if (dataSource instanceof TransactionAwareDataSourceProxy) {
this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
} else {
this.dataSource = dataSource;
}
}
public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
}
public void setTransactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
}
public void setEnvironment(String environment) {
this.environment = environment;
}
public void setScriptingLanguageDrivers(LanguageDriver... scriptingLanguageDrivers) {
this.scriptingLanguageDrivers = scriptingLanguageDrivers;
}
public void setDefaultScriptingLanguageDriver(Class<? extends LanguageDriver> defaultScriptingLanguageDriver) {
this.defaultScriptingLanguageDriver = defaultScriptingLanguageDriver;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new IOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (failFast && event instanceof ContextRefreshedEvent) {
// fail-fast -> check all statements are completed
this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
}
}
private Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) throws IOException {
Set<Class<?>> classes = new HashSet<>();
String[] packagePatternArray = tokenizeToStringArray(packagePatterns,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packagePattern : packagePatternArray) {
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(packagePattern) + "/**/*.class");
for (Resource resource : resources) {
try {
ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
Class<?> clazz = Resources.classForName(classMetadata.getClassName());
if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
classes.add(clazz);
}
} catch (Throwable e) {
LOGGER.warn(() -> "Cannot load the '" + resource + "'. Cause by " + e.toString());
}
}
}
return classes;
}
}
还是带着以下问题来看源码:
- 什么时候完成的
SqlSessionFactoryBuilder对象的创建? - 什么时候完成的
SqlSessionFactory对象的创建? - Mapper层的动态代理对象是何时创建并注入到Spring容器进行管理的?
2.2 什么时候完成的SqlSessionFactoryBuilder对象的创建?
通过SqlSessionFactoryBean类源码我们可以看到,SqlSessionFactoryBuilder作为SqlSessionFactoryBean类的成员属性在对象创建的时候会进行创建。

2.3 什么时候完成的SqlSessionFactory对象的创建并注入Spring容器的?
SqlSessionFactoryBean类实现InitializingBean接口,实现了afterPropertiesSet方法,那么这个类肯定在该方法中实现了某些初始化的逻辑代码, 一块儿看看这个方法
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
// 对SqlSessionFactory对象进行赋值,治理是通过buildSqlSessionFactory实现了SqlSessionFactory对象的创建
this.sqlSessionFactory = buildSqlSessionFactory();
}
再来看看那这个buildSqlSessionFactory方法
这个方法其实就干了下面两件事
- 完成Mybatis全局配置文件的解析, 得到Configuration对象
- 调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
targetConfiguration = this.configuration;
if (targetConfiguration.getVariables() == null) {
targetConfiguration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
targetConfiguration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
} else {
LOGGER.debug(
() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
targetConfiguration = new Configuration();
Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
}
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
if (!isEmpty(this.typeAliases)) {
Stream.of(this.typeAliases).forEach(typeAlias -> {
targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
});
}
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
if (hasLength(this.typeHandlersPackage)) {
scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
.filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
.forEach(targetConfiguration.getTypeHandlerRegistry()::register);
}
if (!isEmpty(this.typeHandlers)) {
Stream.of(this.typeHandlers).forEach(typeHandler -> {
targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
});
}
targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);
if (!isEmpty(this.scriptingLanguageDrivers)) {
Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
targetConfiguration.getLanguageRegistry().register(languageDriver);
LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
});
}
Optional.ofNullable(this.defaultScriptingLanguageDriver)
.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
try {
targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new IOException("Failed getting a databaseId", e);
}
}
Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new IOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new IOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
// 当前方法之前的代码其实就是在完成Mybatis全局配置文件的解析, 得到Configuration对象
// 然后调用sqlSessionFactoryBuilder.build(targetConfiguration)创建SqlSessionFactory对象;
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
2.4 SqlSessionFactory对象是怎么注入Spring容器的?
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
}
我们可以看到SqlSessionFactoryBean实现了FactoryBean接口,
我们都知道有很多种方法可以将Bean对象注入到Spring容器中, 实现FactoryBean接口重写getObject方法就是其中一种, 这里就是利用该方法完成的SqlSessionFactory对象的注入。 看看SqlSessionFactoryBean中getObject`方法的实现
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
afterPropertiesSet()方法中完成了SqlSessionFactory对象的创建。

这样就完成了SqlSessionFactory注入Spring容器。
2.5 什么时候完成的Mapper接口对象的创建并注入Spring容器的?
2.5.1 spring配置文件中关于MapperScannerConfigurer的配置
还是从配置文件applicationContext.xml文件为入口,在applicationContext.xml文件中我们配置了一个Bean为MapperScannerConfigurer
这里我们配置了mapper层的基础扫描路径basePackage


2.5.2 MapperScannerConfigurer类
看看MapperScannerConfigurer类的注释信息, 我给大家翻译了一下, 勉强能说明是什么意思凑合看一下
/**
* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for interfaces and
* registers them as {@code MapperFactoryBean}. Note that only interfaces with at least one method will be registered;
* concrete classes will be ignored.
* BeanDefinitionRegistryPostProcessor,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean。
* 请注意,将仅注册至少具有一个方法的接口;具体实现类将被忽略。
* <p>
* This class was a {code BeanFactoryPostProcessor} until 1.0.1 version. It changed to
* {@code BeanDefinitionRegistryPostProcessor} in 1.0.2. See https://jira.springsource.org/browse/SPR-8269 for the
* details.
* 直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的,
* 但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.
* <p>
* The {@code basePackage} property can contain more than one package name, separated by either commas or semicolons.
* <p>
* basePackage这个属性可包含多个基础包路径名称,使用逗号或分号分隔
* This class supports filtering the mappers created by either specifying a marker interface or an annotation. The
* {@code annotationClass} property specifies an annotation to search for. The {@code markerInterface} property
* specifies a parent interface to search for. If both properties are specified, mappers are added for interfaces that
* match <em>either</em> criteria. By default, these two properties are null, so all interfaces in the given
* {@code basePackage} are added as mappers.
* 此类支持筛选通过指定标记接口或注释创建的映射器。属性annotationClass指定要搜索的注释。markerInterface属性指定要搜索的父接口。
* 如果同时指定了这两个属性,则会为与任一条件匹配的接口添加映射器。
* 默认情况下,这两个属性为 null,因此给定 basePackage中的所有接口都添加为Mapper映射器。
* <p>
* This configurer enables autowire for all the beans that it creates so that they are automatically autowired with the
* proper {@code SqlSessionFactory} or {@code SqlSessionTemplate}. If there is more than one {@code SqlSessionFactory}
* in the application, however, autowiring cannot be used. In this case you must explicitly specify either an
* {@code SqlSessionFactory} or an {@code SqlSessionTemplate} to use via the <em>bean name</em> properties. Bean names
* are used rather than actual objects because Spring does not initialize property placeholders until after this class
* is processed.
* 此配置器为其创建的所有 bean 启用自动装配,以便它们自动装配到正确的SqlSessionFactory或SqlSessionTemplate。
* 但是,如果应用程序中有多个SqlSessionFactory,则无法使用自动装配。
* 在这种情况下,您必须显式指定SqlSessionFactory或SqlSessionTemplate以通过 Bean Name属性使用。
* 使用 Bean Name而不是实际对象,因为 Spring 在处理此类之前不会初始化属性占位符。
* <p>
* Passing in an actual object which may require placeholders (i.e. DB user password) will fail. Using bean names defers
* actual object creation until later in the startup process, after all placeholder substitution is completed. However,
* note that this configurer does support property placeholders of its <em>own</em> properties. The
* <code>basePackage</code> and bean name properties all support <code>${property}</code> style substitution.
* 传入可能需要占位符的实际对象(即数据库用户密码)会失败。
* 使用 Bean Name会将实际的对象创建推迟到启动过程的稍后阶段,在所有占位符替换完成后。
* 但是,请注意,此配置器确实支持其自己的属性的属性占位符。basePackage 和 Bean name 属性都支持 ${property} 样式替换。
* <p>
* Configuration sample:
* 配置示例
* 之前在applicationContext.xml我为什么之后可以这样配置, 就是通过这里的注释说明
* <pre class="code">
* {@code
* <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
* <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
* <!-- optional unless there are multiple session factories defined -->
* <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
* </bean>
* }
* </pre>
*
* @author Hunter Presnall
* @author Eduardo Macarron
*
* @see MapperFactoryBean
* @see ClassPathMapperScanner
*/
注意:
直到mybatis-spring的1.0.1版本之前,当前类都使用的是实现BPP接口(BeanFactoryPostProcessor)来完成这个功能的.
但是在1.0.2版本之后, 我们对其进行了变更, 当前类使用的是实现BFPP(BeanDefinitionRegistryPostProcessor)来完成这个功能.
我这里使用的是mybatis-spring的2.1.0版本, 请注意版本问题。
看一下UML类图

在MapperScannerConfigurer类的注释信息明确告诉我们了,这个类实现了BeanDefinitionRegistryPostProcessor接口,会从Mapper层定义的base包开始递归检索接口并将它们注册为MapperFactoryBean
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,那么他必然实现了postProcessBeanDefinitionRegistry。
2.5.3 MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法**
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// ClassPathBeanDefinitionScanner类的子类,就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中用的。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 下面一堆set方法就是设置ClassPathMapperScanner或从父类ClassPathBeanDefinitionScanner继承的属性值的, 这没啥好说的
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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
if (StringUtils.hasText(defaultScope)) {
scanner.setDefaultScope(defaultScope);
}
scanner.registerFilters();
// 核心代码就是这一行,扫描我们配置的包路径下的所有Mapper接口并注册成MapperFactoryBean对象
// 这里我们配置的basePackage可能有多个,类似这样basePackage = "com.xxxx.mapper,com.xxxx.dao"
// StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)的作用就是将
// basePackage = "com.xxxx.mapper,com.xxxx.dao"分割成 String[]{"com.xxxx.mapper", "com.xxxx.dao"}数组
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
2.5.4 ClassPathMapperScanner类
ClassPathMapperScanner类是ClassPathBeanDefinitionScanner类的子类, 主要用途就是通过 basePackage、annotationClass 或 markerInterface 注册Bean到Spring容器中。
源码中对于该类的注释信息如下:
一个 ClassPathBeanDefinitionScanner,它通过 basePackage、annotationClass 或
markerInterface 注册映射器。如果指定了 annotationClass 和/或
markerInterface,则仅搜索指定的类型(将禁用搜索所有接口)。 此功能以前是 MapperScannerConfigurer
的私有类,但在 1.2.0 版中被分解。
2.5.4.1 scan()方法
看看scanner.scan()方法
实际调用的是ClassPathBeanDefinitionScanner类的scan方法

2.5.4.2 doScan()方法
doScan()方法, 因为ClassPathMapperScanner类对该方法进行了重写, 这里调用的是ClassPathMapperScanner类的doScan()方法, 又在doScan()方法调用了父类的doScan()方法
/**
* Calls the parent search that will search and register all the candidates. Then the registered objects are post
* processed to set them as MapperFactoryBeans
*/
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
+ "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
父类ClassPathBeanDefinitionScanner类的doScan()方法
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
String[] var3 = basePackages;
int var4 = basePackages.length;
// 扫描basePackage路径下的java类文件,先全部转为Resource,然后再判断拿出符合条件的bd
for(int var5 = 0; var5 < var4; ++var5) {
String basePackage = var3[var5];
Set<BeanDefinition> candidates = this.findCandidateComponents(basePackage);
Iterator var8 = candidates.iterator();
while(var8.hasNext()) {
BeanDefinition candidate = (BeanDefinition)var8.next();
// 解析scope属性
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
// 获取beanName
// 先判断注解上有没有显示设置beanName
// 没有的话,就以类名小写为beanName
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 如果这个类是AbstractBeanDefinition类型
// 则为他设置默认值,比如lazy/init/destroy
// 通过扫描出来的bd是ScannedGenericBeanDefinition,实现了AbstractBeanDefinition
if (candidate instanceof AbstractBeanDefinition) {
this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
}
// 如果这个类是AnnotatedBeanDefinition类型
// 处理加了注解的类
// 把常用注解设置到AnnotationBeanDefinition中
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
}
if (this.checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
this.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
通过父类doScan方法的调用获取到了所有的Mapper的接口的BeanDefinition信息。

2.5.4.3 processBeanDefinitions()方法
下面继续执行 processBeanDefinitions(beanDefinitions), 接着看看这个方法
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
AbstractBeanDefinition definition;
BeanDefinitionRegistry registry = getRegistry();
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (AbstractBeanDefinition) holder.getBeanDefinition();
boolean scopedProxy = false;
if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
definition = (AbstractBeanDefinition) Optional
.ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
.map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
"The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
scopedProxy = true;
}
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// 设置BeanDefinition的构造函数参数值为当前BeanDefinition的类名,也就是***Mapper接口的类名
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
try {
// for spring-native
definition.getPropertyValues().add("mapperInterface", Resources.classForName(beanClassName));
} catch (ClassNotFoundException ignore) {
// ignore
}
// 设置BeanDefinition的beanClass为MapperFactoryBean.class
// 这里一修改, 就相当于在创建Bean对象的时候都是通过MapperFactoryBean来创建的
definition.setBeanClass(this.mapperFactoryBeanClass);
// 添加属性addToConfig为true, 因为ClassPathMapperScanner类的addToConfig属性默认为true
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// Attribute for MockitoPostProcessor
// https://github.com/mybatis/spring-boot-starter/issues/475
definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
boolean explicitFactoryUsed = false;
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) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
// 设置属性按类型自动注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
definition.setLazyInit(lazyInitialization);
if (scopedProxy) {
continue;
}
if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
definition.setScope(defaultScope);
}
if (!definition.isSingleton()) {
BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
registry.removeBeanDefinition(proxyHolder.getBeanName());
}
registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
}
}
}

有上述方法我们可以知道,Mapper层接口的Bean对象都是通过MapperFactoryBean来创建的, mapper层的动态代理对象肯定也是借助MapperFactoryBean去完成了.
2.6 MapperFactoryBean是如何完成代理对象创建并注入Spring容器的?
首先看下 MapperFactoryBean的类图

发现这个类即实现了FactoryBean接口, 也实现了InitializingBean接口, 那就一起来看看分别在
getObject()和afterPropertiesSet()中都做了什么逻辑处理
2.6.1 MapperFactoryBean与SqlSessionFactoryBean是如何关联的
2.6.1.1 MapperFactoryBean的afterPropertiesSet方法
MapperFactoryBean类没有该方法, 肯定是在它的父类中实现的,在DaoSupport类中可以找到该方法
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
// 本类中的该方法是个抽象的方法,这里肯定调用的是子类的方法(使用了模板方法模式)
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
这个checkDaoConfig()方法在SqlSessionDaoSupport类中紧紧只是进行了sqlSessionTemplate是否注入完成的判断, 在SqlSessionDaoSupport类的子类MapperFactoryBean中又进行了重写
MapperFactoryBean类中的checkDaoConfig方法
@Override
protected void checkDaoConfig() {
// 调用了父类的该方法检测sqlSessionTemplate是否已经注入
super.checkDaoConfig();
// 检查mapperInterface属性是否已经填充
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// 这里就是调用父类的sqlSessionTemplate对象的getConfiguration方法获取Configuration对象
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 这里不就是Mybatis框架里面的addMapper方法么, 到这是不是就明白了。
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
在MapperFactoryBean注入到容器之后,MapperFactoryBean类中继承自父类的的afterPropertiesSet方法被自动执行, 当前MapperFactoryBean对象映射的接口就被注册到Mybatis框架的mapper注册器中了。




2.6.1 MapperFactoryBean的getObject()方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
在Spring框架扫描所有实现FactoryBean接口的类进行Bean对象的注册时, 这个getObject()方法会被执行,会调用Mybatis框架的通过Mybatis框架的动态代理模式创建mapper接口的代理对象注入到Spring容器中进行托管。

到此,Mybatis框架中的核心对象都注册到Spring容器中, 使用的时候是需要注入到调用的类中, 就可以使用对应的方法实现对数据库的增删改查了。
3 关于InitializingBean接口的拓展知识
Spring框架为bean提供了两种初始化bean的方式,实现InitializingBean接口或者通过在XML配置文件中添加init-method的方式,这两种方式可以同时使用。
实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。
如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。
InitializingBean是Spring框架提供的拓展接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
我们可以利用这个来完成很多功能, 比如在中小型系统业务开发中实现数据库的读写分离等业务操作。



















