【Spring源码】SpringBoot_MyBatis_starter源码(一)

准备一下

由于我们使用 MyBatis 的时候,很少独立使用,所以我直接从 SpringBoot 自动装配,进入 MyBatis 来看看。 直接看 org.mybatis.spring.boot:mybatis-spring-boot-starter 源码目录结构

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
├── LICENSE
├── README.md
├── license.txt
├── mvnw
├── mvnw.cmd
├── mybatis-spring-boot-autoconfigure
│   ├── format.xml
│   ├── license.txt
│   ├── pom.xml
│   └── src
├── mybatis-spring-boot-samples
│   ├── mybatis-spring-boot-sample-annotation
│   ├── mybatis-spring-boot-sample-freemarker
│   ├── mybatis-spring-boot-sample-freemarker-legacy
│   ├── mybatis-spring-boot-sample-groovy
│   ├── mybatis-spring-boot-sample-kotlin
│   ├── mybatis-spring-boot-sample-thymeleaf
│   ├── mybatis-spring-boot-sample-velocity
│   ├── mybatis-spring-boot-sample-velocity-legacy
│   ├── mybatis-spring-boot-sample-war
│   ├── mybatis-spring-boot-sample-web
│   ├── mybatis-spring-boot-sample-xml
│   └── pom.xml
├── mybatis-spring-boot-starter
│   ├── license.txt
│   └── pom.xml
├── mybatis-spring-boot-starter-test
│   └── pom.xml
├── mybatis-spring-boot-test-autoconfigure
│   ├── format.xml
│   ├── pom.xml
│   └── src
├── pom.xml
└── travis
├── after_success.sh
└── settings.xml

mybatis-spring-boot-samples/mybatis-spring-boot-sample-xml 中刚好有我想要的示例,刚好不用自己写了,就使用这个示例来查看吧。

自动发现

从之前的自动发现文章中,我们发现,通常自动装配的类都写在 META-INF/spring.factories 这里面,现在我们可以直接查看 mybatis-spring-boot-autoconfigure 这里面的这个文件:

1
2
3
4
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

通过名字,感觉 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 在装载的过程中比较重要。 这个类里面有个内部类,是 MyBatis 切入 SpringBoot 的重点:

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
40
41
42
43
44
45
46
47
48
49
50
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

private BeanFactory beanFactory;

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}

logger.debug("Searching for mappers annotated with @Mapper");

List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors())
// Need to mybatis-spring 2.0.2+
.filter(x -> x.getName().equals("lazyInitialization")).findAny()
.ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}

@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

}

@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}

}

这个类实现了两个接口:

  1. BeanFactoryAware:这个接口,被 Spring 扫描到的话,是会被 Spring 调用的,传递了当前 BeanFactory 实例,有助于我们后期可以对 BeanFactory 的操作;
  2. ImportBeanDefinitionRegistrar:那这个接口还真的是第一次出现,他是一个在解析 Configuration,可以注册业务需要的 BeanDefinition,简单的说,在刷新容器的时候,BeanDefinitionRegistryPostProcessors 会被调用到,而 SpringBoot 中,总是有个 ConfigurationClassPostProcessor 存在,他在解析配置的时候,会手动的去触发 ImportBeanDefinitionRegistrar#registerBeanDefinitions 函数,这时候,上面的内部类就会被读取到。

那这个类他就是负责在解析配置类的时候,注册了一个 MapperScannerConfigurer 类,看名字不用猜就知道是扫描项目中被 @Mapper 注解装饰的接口类了。

配置 SqlSession

上面注册的 MapperScannerConfigurer 是一个 BeanDefinitionRegistryPostProcessor 处理器,他会在解析配置后面,被 BeanFactory 调用到。 调用就是扫描 Mapper 的存在了。

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
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
// ...
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}

// 创建扫描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
// 目前SqlSessionFactory和SqlSessionTemplate均为Null
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));
}
// 注册扫描过滤器,目前是加上扫描 @Mapper 的接口类
scanner.registerFilters();
// 开始扫描
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

doScan(basePackages);

// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
// 一个空处理,目的是为了注册注解配置类吧?
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

由于 ClassPathMapperScanner 继承了 Spring 提供的 ClassPathBeanDefinitionScanner,所以可以直接把扫描注解这件事情交给 Spring 来做,

1
2
3
4
5
6
7
8
9
10
11
12
13
@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;
}

那由于上面已经扫描了大半的 BeanDefinition,后面就新增了还需要扫描 @Mapper 的注解,这时候重新进行一遍扫描以后,新增加的 BeanDefinition 就是我们要的 Mapper 接口了。所以这时候拿到了所有的 MapperBeanDefinition 进行处理。 接下来对扫描到 Mapper 进行处理一波:

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
40
41
42
43
44
45
46
47
48
49
50
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");

// Bean是接口,但是其实现类是 MapperFactoryBean,所以这里设置 mapperFactoryBean 进去所有的 Mapper里面
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);

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

boolean explicitFactoryUsed = false;
// 这下面目前均为空,所以都没有进入if里面
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);
}
}

获取Mapper

实例化在 ApplicationContext#finishBeanFactoryInitialization 中这一步实现 finishBeanFactoryInitialization。这时候需要先看下 MyBatis 自动配置配置了哪些 Bean

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class MybatisAutoConfiguration implements InitializingBean {

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}

return factory.getObject();
}

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

}

SqlSessionFactorySqlSessionTemplate,先来看看这两个类的作用: 这两个类主要围绕构建 SqlSession 来做一些配置的。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public interface SqlSession extends Closeable {

<T> T selectOne(String statement);

<T> T selectOne(String statement, Object parameter);

<E> List<E> selectList(String statement);

<E> List<E> selectList(String statement, Object parameter);

<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

<K, V> Map<K, V> selectMap(String statement, String mapKey);

<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

<T> Cursor<T> selectCursor(String statement);

<T> Cursor<T> selectCursor(String statement, Object parameter);

<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

void select(String statement, Object parameter, ResultHandler handler);

void select(String statement, ResultHandler handler);

void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

int insert(String statement);

int insert(String statement, Object parameter);

int update(String statement);

int update(String statement, Object parameter);

int delete(String statement);

int delete(String statement, Object parameter);

void commit();

void commit(boolean force);

void rollback();

void rollback(boolean force);

List<BatchResult> flushStatements();

@Override
void close();

void clearCache();

Configuration getConfiguration();

<T> T getMapper(Class<T> type);

Connection getConnection();
}

这是 MyBatis 执行 SQL 的关键接口,定义了 jdbc 常用的方法。而 SqlSessionTemplate 就是一个 SqlSession,专门用来整合 Spring 的一个 SqlSession,而 SqlSessionFactory 则是封装了连接的方法以及一些其他的配置,比如拦截器等等。 那我们看看主启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SpringBootApplication
public class SampleXmlApplication implements CommandLineRunner {

public static void main(String[] args) {
SpringApplication.run(SampleXmlApplication.class, args);
}

private final CityDao cityDao;

private final HotelMapper hotelMapper;

public SampleXmlApplication(CityDao cityDao, HotelMapper hotelMapper) {
this.cityDao = cityDao;
this.hotelMapper = hotelMapper;
}

@Override
@SuppressWarnings("squid:S106")
public void run(String... args) {
System.out.println(this.cityDao.selectCityById(1));
System.out.println(this.hotelMapper.selectByCityId(1));
}

}

他注入了一个 CityDao 以及 一个 HotelMapper,两种不同的查询方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class CityDao {

private final SqlSession sqlSession;

public CityDao(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}

public City selectCityById(long id) {
return this.sqlSession.selectOne("selectCityById", id);
}

}

@Mapper
public interface HotelMapper {

Hotel selectByCityId(int cityId);

}

那么,Spring 容器在初始化 SampleXmlApplication 的时候就会去循环递归的去找到最后所需要的依赖,那其实 CityDao 的初始化并不难,现在看看 HotelMapper 的初始化。 HotelMapper 在注册给 Spring 的时候其实已经将实际对象的 FactoryBean 设置成 MapperFactoryBean,而 MapperFactoryBean 实现了 FactoryBeanSpring 将会通过调用 getObject 来获取工厂构造的对象,所以这时候我们看看 MapperFactoryBean 怎么实现这个方法的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

// 我们自己写的Mapper接口类
private Class<T> mapperInterface;

private boolean addToConfig = true;

public MapperFactoryBean() {
// intentionally empty
}

public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

// FactoryBean需要实现这个方法
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

}

OK,我们可以看到他从 SqlSession 中来获取 Mapper代理,所以这个方法在这里充其量就是一个桥梁,架通了 MyBatis 构造的代理以及 SpringBean 的桥。 那 SqlSession 怎么来的,又是一个 Factory 构造来的,还记得上面的工厂配置类吗,就是那里构造出来的,现在我们需要回去去看看他怎么构造 MyBatis 上下文的:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 构造一个SqlSessionFactory
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// 数据源
factory.setDataSource(dataSource);
// 提供一个访问SpringBoot资源的实现类,是个VFS,MyBatis定义访问资源的一个抽象类
factory.setVfs(SpringBootVFS.class);
// MyBatis配置文件:mybatis.config-location=classpath:mybatis-config.xml
// 在applicationContext.yml中的配置
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
// 读取MyBatisProperties配置,此时SpringBoot已经将配置文件中的信息转换为JavaBean
// 不过我们没有配置什么东西
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
// MyBatis拦截器
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
// 主键Id生成器
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
// 接收数据JavaBean所在的包
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
// 接收数据JavaBean父类
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
// 类型处理器所在的包
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
// 手动指定的类型处理器
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
// Mapper.xml所在的位置
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
// 判断使用的自动SQL脚本驱动,看到这里我才知道SQL模板不仅支持xml,还支持FreeMarker/thymeleaf等模板引擎
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}

// 构建SqlSessionFactory,在这里做了MyBatis上下文的初始化。
return factory.getObject();
}

来到这里,终于进来了 MyBatis 的内容了,就是开始读取配置,解析,然后这个 Configuration 类将贯穿相应使用 MyBatis 的整个生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// SqlSessionFactoryBean.Java
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}

return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
// 校验连接池是否存在
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
// 'configuration' and 'configLocation'不能同时声明
state((configuration == null && configLocation == null) !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");

// 开始构建
this.sqlSessionFactory = buildSqlSessionFactory();
}

目前官方示例给的一个配置就是简单的扫描接收类型的包以及 Mapper 映射的路径:

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="sample.mybatis.domain"/>
</typeAliases>
<mappers>
<mapper resource="sample/mybatis/mapper/CityMapper.xml"/>
<mapper resource="sample/mybatis/mapper/HotelMapper.xml"/>
</mappers>
</configuration>

进入 buildSqlSessionFactory 我们就可以看到配置熟悉的东西都在这里被解析,这个代码没点 j8 基础都看不懂了:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// SqlSessionFactoryBean.Java
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

final Configuration targetConfiguration;

// Builder模式
XMLConfigBuilder xmlConfigBuilder = null;
// 如果我们配置了 configuration 对象,这里可以减少xml的读取,有效提速吧
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,与下面的SpringBoot配置一起解析
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
// 还没解析,只是先把默认的 Configuration 引用拿到
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);
// SpringBoot扫描的资源了
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

// ------------- 下面是通过SpringBoot自动配置的方式去整合xml的配置了 -------------
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 + "'");
});
}

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 NestedIOException("Failed getting a databaseId", e);
}
}

Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

if (xmlConfigBuilder != null) {
try {
// 解析xml,将配置应用到Configuration中
xmlConfigBuilder.parse();
LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
} catch (Exception ex) {
throw new NestedIOException("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 NestedIOException("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.");
}

// 方法就是简单的将配置放入 DefaultSqlSessionFactory 中返回出去
// 用于在项目中需要开启SqlSession的时候可以用到
return this.sqlSessionFactoryBuilder.build(targetConfiguration);

/*
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
*/


}

解析Configuration.xml

1
2
3
4
5
6
7
8
9
// XMLConfigBuilder.java
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// XMLConfigBuilder.java
private void parseConfiguration(XNode root) {
try {
// 解析属性元素
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

这里大概就是 Configuration.xml 的所有内容的解析,那我们就直接看看解析 SqlMapper 的方法。

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
// <mapper resource="sample/mybatis/mapper/HotelMapper.xml"/>
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 获取 resource
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// 来到这里开始解析
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析的重点
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}

解析Mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// XMLMapperBuilder.java
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 解析解析所有标签
configurationElement(parser.evalNode("/mapper"));
// 标记已经解析完成
configuration.addLoadedResource(resource);
// 注册接口和xml绑定的类型
bindMapperForNamespace();
}

// 解析ResultMaps
parsePendingResultMaps();
// 解析缓存
parsePendingCacheRefs();
// 解析SQL语句
parsePendingStatements();
}

解析Statement语句

主要是解析标签比较重要,我们现在进入来看看 configurationElement(parser.evalNode("/mapper")) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("selectinsertupdatedelete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}

结合 Mapper.xml 来看看:

1
2
3
4
5
6
7
8
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis.mapper.HotelMapper">
<select id="selectByCityId" resultType="Hotel">
select city, name, address, zip from hotel where city = #{id}
</select>
</mapper>

解析语句是发生在 buildStatementFromContext 里面的:

1
2
3
4
5
6
7
private void buildStatementFromContext(List<XNode> list) {
// 根据不同的数据库id来绑定语句
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}

不得不说,MyBatis 还真的是喜欢 Builder

1
2
3
4
5
6
7
8
9
10
11
12
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 循环每一个 statement 标签,开始解析
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 发生解析,将 mapper.xml 的信息存入 Configuration
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());

String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);

String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);

// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);

// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}

// 构建SQLSource对象,这个接口将动态SQL和静态的划分成两个不同的对象
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 获取判断是否是预编译的SQL语句
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取返回的对象类型
String resultType = context.getStringAttribute("resultType");
// 结合配置文件中的typeAliases来获取到Class对象
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");

// 通过builderAssistant将Mapper对象塞入Configuration
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

通过构建好的配置信息,将查询的配置加入 Configuration

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// builderAssistant.addMappedStatement
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {

if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}

// 获取id,sample.mybatis.mapper.HotelMapper.selectByCityId
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);

ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}

MappedStatement statement = statementBuilder.build();
// 存入configuration
configuration.addMappedStatement(statement);
return statement;
}

注册Mapper到MyBatis的Registry

Configuration 中,解析 Mapper 已经完成,接下来就是注册一个接口代理对象到 Configuration 中去:

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
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 解析接口类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 将当前接口类型添加到Configuration
configuration.addMapper(boundType);
}
}
}
}

重点就在于这个 `configuration.addMapper` 中:

public class Configuration {
// ...
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class MapperRegistry {
// ...
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 使用 MapperProxyFactory 封装接口类型,加入Registery中
knownMappers.put(type, new MapperProxyFactory<>(type));
// 加入后,继续解析这个接口,看看有没有 @Select 之类的注解存在
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
// ...
}

MapperFactoryBean对接Spring

还记得上面对接 SpringgetBean 的对接桥梁吗,就是 MapperFactoryBean 来适配接口的:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
// ...
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// ...
}

public class SqlSessionTemplate implements SqlSession, DisposableBean {
// ...
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
// ...
}

public class Configuration {
// ...
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// ...
}

public class MapperRegistry {
// ...
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// ...
}

public class MapperProxyFactory<T> {
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}

到达这里,初始化工作基本已经完成,现在我们要看看运行时怎么获取数据的


获取代理对象

那么现在就回到主程序中,来观察 MyBatis 是怎么封装 Mapper 接口来查询数据的,这里我重新贴一下主程序中的查询入口:

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
@SpringBootApplication
public class SampleXmlApplication implements CommandLineRunner {

public static void main(String[] args) {
SpringApplication.run(SampleXmlApplication.class, args);
}

private final CityDao cityDao;

private final HotelMapper hotelMapper;

public SampleXmlApplication(CityDao cityDao, HotelMapper hotelMapper) {
this.cityDao = cityDao;
this.hotelMapper = hotelMapper;
}

@Override
@SuppressWarnings("squid:S106")
public void run(String... args) {
System.out.println(this.cityDao.selectCityById(1));
// 常用的是这种方式,我直接看这里了
System.out.println(this.hotelMapper.selectByCityId(1));
}

}

刚刚上面提到过,MyBatis 在整合 Spring 的时候其实就将自己 Registry 中的 MapperProxy 给放入 BeanDefinition 中,然后交给 Spring 去自动注入到需要依赖的对象。那么现在拿到的对象就是一个 MapperProxy。 既然是 MapperProxy,那要看怎么封装查询数据就是看这个了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

MapperProxy 是何方神圣,哦,是一个 InvocationHandler,这是一个我从来没见过的 jdk 接口。J8 专有的动态代理的接口,那他有什么用呢,就是将实现封装在这个实现类里边,实现类有个 invoke 方法需要实现,那么实现类的所有方法被调用时,都需要先走这个方法,这时候我们就可以在 invoke 里面做一些羞羞的事情。

InvocationHandler代理

那就来示例一下这个东西怎么用:

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
// 一个普通的接口
public interface A {
void add();
}

// 代理处理器
public class InvokeHandler<T> implements InvocationHandler {

Class<T> type;

public InvokeHandler(Class<T> tClass) {
this.type = tClass;
}

// 将会拦截接口的方法
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("调用到这里来了");
return null;
}

// 创建一个代理对象,接口中方法被调用的时候,均为走上面的invoke方法
public T getProxy() {
return (T) Proxy.newProxyInstance(type.getClassLoader(),
new Class[]{type}, this);
}

}


public class TestInvoke {

public static void main(String[] args) {
A proxy = new InvokeHandler<>(A.class).getProxy();
proxy.add();// 输出:调用到这里来了
}

}

那就是说,MapperProxy 拦截了我们所有接口的调用,然后,从这里读取了 mapper.xml 中配置的 SQL 语句,然后调用 jdbc 做相对应的查询。

获取MapperMethod

OK,弄清楚了动态代理,现在回来看看怎么被调用的:

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
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 上面是j8后新增的默认方法的读取,以及判断代理的方法是否是对象
// 如果哪个为是,直接调用实现的方法

// 这里开始就是MyBatis自己的实现
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}


private MapperMethod cachedMapperMethod(Method method) {
// 从缓存命中,如果没有就创建
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}

MapperMethod调用

先来看看 MapperMethod 有什么属性,方法肯定就是上面的 execute 是重中之重:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class MapperMethod {

// 这是两个内部类对象,一个封装SQL一个封装方法的签名
private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据不同的SQL类型去调用不同的方法
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 查询进入这里
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 由于主程序只是查询一个,所以跑这里来了

// 如果带有@Param则要根据名字匹配,如果没有,则根据args的顺序来查找
Object param = method.convertArgsToSqlCommandParam(args);
// sqlSession=SqlSessionTemplate,一个对接Spring事务管理器的对象
result = sqlSession.selectOne(command.getName(), param);
// 如果是j8的Optional返回的话,需要使用Optional封装结果
if (method.returnsOptional()
&& (result == null !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// ......
}

SqlSession拦截器

上面的代码我们来到了 sqlSession.selectOne 这句话,所以进入代码看看是发生什么事情:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class SqlSessionTemplate implements SqlSession, DisposableBean {

//...
@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
//...

/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
* unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
* {@code PersistenceExceptionTranslator}.
* 翻译:代理需要路由MyBatis去调用SpringTX产生出来的SqlSession
*/
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用下面的方法去获取兼容SpringTX的SqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行方法内容 真正的SqlSession的SelectOne函数
Object result = method.invoke(sqlSession, args);
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) {
// 也是关闭回滚
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
// 关闭SqlSession并且将连接放回连接池
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {

notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

// 获取SpringTX事务的适配器
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}

LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);

// 如果有Spring的事务注解的话,则会将事务注册到SpringTX模块中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

return session;
}

}

查询方法

好,总体的开启 SqlSession 啊,对接事务的,有个了解,现在具体到怎么查询的问题,那就是 SqlSession 了。 算了,下集一起说……. To Be Continue...