在使用Mybatis时,我们通常将其配置在Spring容器中,当Spring启动的时候会自动加载Mybatis的所有配置文件然后生成注入到Spring中的Bean,本文从实用的角度进行Mybatis源码解读,会关注以下一些方面:
- Mybatis都有哪些配置文件和配置项
- Mybatis初始化的源码流程;
- Mybatis初始化后,产生了哪些对象;
Mybatis初始化环境并且执行SQL语句的JAVA代码
先看一段初始化Mybatis环境并且执行SQL语句的Java代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package
org
.
apache
.
ibatis
.
session
;
import
java
.
io
.
Reader
;
import
org
.
apache
.
ibatis
.
io
.
Resources
;
public
class
MyTest
{
public
static
void
main
(
String
[
]
args
)
throws
Exception
{
// 开始初始化
final
String
resource
=
"org/apache/ibatis/builder/MapperConfig.xml"
;
final
Reader
reader
=
Resources
.
getResourceAsReader
(
resource
)
;
SqlSessionFactory
sqlMapper
=
new
SqlSessionFactoryBuilder
(
)
.
build
(
reader
)
;
// 开始执行SQL
SqlSession
session
=
sqlMapper
.
openSession
(
)
;
Integer
count
=
session
.
selectOne
(
"org.apache.ibatis.domain.blog.mappers.BlogMapper.selectCountOfPosts"
)
;
System
.
out
.
println
(
count
)
;
}
}
|
这段代码完成了这些事情:
1、读取Mybatis的配置文件
2、构建SqlSessionFactory
3、从SqlSessionFactory中创建一个SqlSession
4、使用SqlSession执行一个select语句,参数是Mapper.java的一个方法名
5、打印结果
在这里前三行代码包括读取配置文件和创建SqlSessionFactory,这就是Mybatis的一次初始化过程。
如果查看一下Spring配置Mybatis的文件,就会发现它使用mybatis-spring的包也主要是初始化了这个SqlSessionFactory对象:
1
2
3
4
5
6
7
8
9
|
|
该Spring配置sqlSessionFactory接收了三个参数,分别是数据源dataSource、Mybatis的主配置文件MapperConfig.xml、mapper.xml文件的扫描路径。
可以看出Mybatis的初始化过程就是读取配置文件然后构建出sqlSessionFactory的过程。
Mybatis都有哪些配置文件和配置项?
上面的Java代码中初始化Mybatis只使用了配置文件MapperConfig.xml,然而在Spring配置文件中构建sqlSessionFactory时也使用了mapper.xml配置文件,其实Mybatis最多也就这两类文件,主配置文件MapperConfig.xml可以通过
主配置文件:MapperConfig.xml
一个包含了所有属性的MapperConfig.xml实例:
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
|
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
handler
=
"org.apache.ibatis.builder.CustomStringTypeHandler"
/>
|
主配置文件只有一个XML节点,就是configuration,它包含9种配置项:
- properties 属性:在这里配置的属性可以在整个配置文件中使用来替换需要动态配置的属性值
- settings 设置:MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,比如是否使用缓存和日志记录的方式
- typeAliases 类型命名:类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
- typeHandlers 类型处理器:无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
- objectFactory 对象工厂:MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
- plugins 插件:MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用,比如增加分页功能、格式化输出最终的SQL等扩展;
- environments 环境:MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,比如设置不同的开发、测试、线上配置,在每个配置中可以配置事务管理器和数据源对象;
- databaseIdProvider 数据库厂商标识:MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
- mappers 映射器:MyBatis 的行为已经由上述元素配置完了,通过这里配置的mappers文件,我们可以去查找定义 的SQL 映射语句用于执行;
可以看出,前8个配置项用户设定Mybatis运行的一些环境,而第9个mappers映射器才是需要执行的SQL的配置,在正常情况下,我们只需要配置第9个mapper映射器的地址即可,前面的的Mybatis行为配置都有默认值正常情况下不需要设定。
包含最终SQL的mapper映射器配置文件
虽然我们经常写mapper文件,知道有select/insert/update/delete四种元素,还有sql/resultmap等配置项,就感觉配置项好多好多,但其实mapper总共也就8种配置,我们常用的6种就包含在内:
- cache – 给定命名空间的缓存配置。
- cache-ref – 其他命名空间缓存配置的引用。
- resultMap – 是最复杂也是最强大的元素,用于实现数据库表列和Java Bean的属性名的映射配置;
- sql – 可被其他语句引用的可重用语句块。
- insert – 映射插入语句
- update – 映射更新语句
- delete – 映射删除语句
- select – 映射查询语句
正常情况下,我们很少使用Mybatis提供的cache机制而是使用外部的Redis等缓存,所以这里的1和2的cache配置几乎不会使用,主要也就是我们平时使用的6种配置。
以上就是Mybatis所有提供给我们配置的地方,改变Mybatis行为的有8个配置项,每个XML配置文件刚好也最多有8个配置项,总共有16个配置项。
Mybatis初始化的源码流程
阅读Mybatis源码最好的方式,就是从源码中的单测作为入口,然后DEBUG一步步的执行,在自己关注的地方多多停留一会仔细查看。
以下以代码的流程进行解析,只贴出主要的代码块:
Mybatis代码初始化入口
1
2
3
4
5
6
7
|
@BeforeClass
public
static
void
setup
(
)
throws
Exception
{
createBlogDataSource
(
)
;
final
String
resource
=
"org/apache/ibatis/builder/MapperConfig.xml"
;
final
Reader
reader
=
Resources
.
getResourceAsReader
(
resource
)
;
sqlMapper
=
new
SqlSessionFactoryBuilder
(
)
.
build
(
reader
)
;
}
|
这里看到,进入了new SqlSessionFactoryBuilder().build(reader)方法。
进入SqlSessionFactoryBuilder的build方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
SqlSessionFactory
build
(
Reader
reader
,
String
environment
,
Properties
properties
)
{
try
{
XMLConfigBuilder
parser
=
new
XMLConfigBuilder
(
reader
,
environment
,
properties
)
;
return
build
(
parser
.
parse
(
)
)
;
}
catch
(
Exception
e
)
{
throw
ExceptionFactory
.
wrapException
(
"Error building SqlSession."
,
e
)
;
}
finally
{
ErrorContext
.
instance
(
)
.
reset
(
)
;
try
{
reader
.
close
(
)
;
}
catch
(
IOException
e
)
{
// Intentionally ignore. Prefer previous error.
}
}
}
|
主要两行在try块内,第一行的内容是调用XPathParser加载了Mybatis的主配置文件,而第二步包含两个步骤,parser.parse()方法返回的是一个Configuration对象,包裹它的build方法只有一行代码:
1
2
3
4
|
public
SqlSessionFactory
build
(
Configuration
config
)
{
return
new
DefaultSqlSessionFactory
(
config
)
;
}
|
这就可以看出,其实初始化过程就是创建Configuration对象的过程,对照MapperConfig.xml的根元素是
Mybatis核心对象Configuration的构建过程
接下来进入了XMLConfigBuilder.parse()方法,该方法解析XML文件的/configuration节点,然后挨个解析了上面配置文件中提到的9大配置:
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
|
public
Configuration
parse
(
)
{
if
(
parsed
)
{
throw
new
BuilderException
(
"Each XMLConfigBuilder can only be used once."
)
;
}
parsed
=
true
;
parseConfiguration
(
parser
.
evalNode
(
"/configuration"
)
)
;
return
configuration
;
}
private
void
parseConfiguration
(
XNode
root
)
{
try
{
// issue #117 read properties first
propertiesElement
(
root
.
evalNode
(
"properties"
)
)
;
Properties
settings
=
settingsAsProperties
(
root
.
evalNode
(
"settings"
)
)
;
loadCustomVfs
(
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
)
;
}
}
|
我们挨个查看,这些配置项的解析,都产出了什么内容;
1、properties配置项的解析
进入propertiesElement方法,我们发现初始化了一个Properties对象,将XML中所有的子节点按照KEY-VALUE存入properties之后,和Configuration.variables变量进行了合并,而Configuration.variables本身,也是个Properties对象;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private
void
propertiesElement
(
XNode
context
)
throws
Exception
{
if
(
context
!=
null
)
{
Properties
defaults
=
context
.
getChildrenAsProperties
(
)
;
String
resource
=
context
.
getStringAttribute
(
"resource"
)
;
String
url
=
context
.
getStringAttribute
(
"url"
)
;
if
(
resource
!=
null
&&
url
!=
null
)
{
throw
new
BuilderException
(
"The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."
)
;
}
if
(
resource
!=
null
)
{
defaults
.
putAll
(
Resources
.
getResourceAsProperties
(
resource
)
)
;
}
else
if
(
url
!=
null
)
{
defaults
.
putAll
(
Resources
.
getUrlAsProperties
(
url
)
)
;
}
Properties
vars
=
configuration
.
getVariables
(
)
;
if
(
vars
!=
null
)
{
defaults
.
putAll
(
vars
)
;
}
parser
.
setVariables
(
defaults
)
;
configuration
.
setVariables
(
defaults
)
;
}
}
|
将properties配置解析后合并到Configuration.variables之后,后续的配置文件都可以使用这些变量。
2、setting的配置读取
setting配置的读取,包含两个步骤,第一步,将XML中所有的配置读取到properties对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
private
Properties
settingsAsProperties
(
XNode
context
)
{
if
(
context
==
null
)
{
return
new
Properties
(
)
;
}
Properties
props
=
context
.
getChildrenAsProperties
(
)
;
// Check that all settings are known to the configuration class
MetaClass
metaConfig
=
MetaClass
.
forClass
(
Configuration
.
class
,
localReflectorFactory
)
;
for
(
Object
key
:
props
.
keySet
(
)
)
{
if
(
!
metaConfig
.
hasSetter
(
String
.
valueOf
(
key
)
)
)
{
throw
new
BuilderException
(
"The setting "
+
key
+
" is not known. Make sure you spelled it correctly (case sensitive)."
)
;
}
}
return
props
;
}
|
这个函数读取了setting的配置项,通过反射访问Configuration.class,如果不存在某个配置项的set方法则报错;
然后在settingsElement方法中,将这些读取的配置项存入了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
|
private
void
settingsElement
(
Properties
props
)
throws
Exception
{
configuration
.
setAutoMappingBehavior
(
AutoMappingBehavior
.
valueOf
(
props
.
getProperty
(
"autoMappingBehavior"
,
"PARTIAL"
)
)
)
;
configuration
.
setAutoMappingUnknownColumnBehavior
(
AutoMappingUnknownColumnBehavior
.
valueOf
(
props
.
getProperty
(
"autoMappingUnknownColumnBehavior"
,
"NONE"
)
)
)
;
configuration
.
setCacheEnabled
(
booleanValueOf
(
props
.
getProperty
(
"cacheEnabled"
)
,
true
)
)
;
configuration
.
setProxyFactory
(
(
ProxyFactory
)
createInstance
(
props
.
getProperty
(
"proxyFactory"
)
)
)
;
configuration
.
setLazyLoadingEnabled
(
booleanValueOf
(
props
.
getProperty
(
"lazyLoadingEnabled"
)
,
false
)
)
;
configuration
.
setAggressiveLazyLoading
(
booleanValueOf
(
props
.
getProperty
(
"aggressiveLazyLoading"
)
,
false
)
)
;
configuration
.
setMultipleResultSetsEnabled
(
booleanValueOf
(
props
.
getProperty
(
"multipleResultSetsEnabled"
)
,
true
)
)
;
configuration
.
setUseColumnLabel
(
booleanValueOf
(
props
.
getProperty
(
"useColumnLabel"
)
,
true
)
)
;
configuration
.
setUseGeneratedKeys
(
booleanValueOf
(
props
.
getProperty
(
"useGeneratedKeys"
)
,
false
)
)
;
configuration
.
setDefaultExecutorType
(
ExecutorType
.
valueOf
(
props
.
getProperty
(
"defaultExecutorType"
,
"SIMPLE"
)
)
)
;
configuration
.
setDefaultStatementTimeout
(
integerValueOf
(
props
.
getProperty
(
"defaultStatementTimeout"
)
,
null
)
)
;
configuration
.
setDefaultFetchSize
(
integerValueOf
(
props
.
getProperty
(
"defaultFetchSize"
)
,
null
)
)
;
configuration
.
setMapUnderscoreToCamelCase
(
booleanValueOf
(
props
.
getProperty
(
"mapUnderscoreToCamelCase"
)
,
false
)
)
;
configuration
.
setSafeRowBoundsEnabled
(
booleanValueOf
(
props
.
getProperty
(
"safeRowBoundsEnabled"
)
,
false
)
)
;
configuration
.
setLocalCacheScope
(
LocalCacheScope
.
valueOf
(
props
.
getProperty
(
"localCacheScope"
,
"SESSION"
)
)
)
;
configuration
.
setJdbcTypeForNull
(
JdbcType
.
valueOf
(
props
.
getProperty
(
"jdbcTypeForNull"
,
"OTHER"
)
)
)
;
configuration
.
setLazyLoadTriggerMethods
(
stringSetValueOf
(
props
.
getProperty
(
"lazyLoadTriggerMethods"
)
,
"equals,clone,hashCode,toString"
)
)
;
configuration
.
setSafeResultHandlerEnabled
(
booleanValueOf
(
props
.
getProperty
(
"safeResultHandlerEnabled"
)
,
true
)
)
;
configuration
.
setDefaultScriptingLanguage
(
resolveClass
(
props
.
getProperty
(
"defaultScriptingLanguage"
)
)
)
;
configuration
.
setCallSettersOnNulls
(
booleanValueOf
(
props
.
getProperty
(
"callSettersOnNulls"
)
,
false
)
)
;
configuration
.
setUseActualParamName
(
booleanValueOf
(
props
.
getProperty
(
"useActualParamName"
)
,
true
)
)
;
configuration
.
setReturnInstanceForEmptyRow
(
booleanValueOf
(
props
.
getProperty
(
"returnInstanceForEmptyRow"
)
,
false
)
)
;
configuration
.
setLogPrefix
(
props
.
getProperty
(
"logPrefix"
)
)
;
@SuppressWarnings
(
"unchecked"
)
Class
<
?
extends
Log
>
logImpl
=
(
Class
<
?
extends
Log
>
)
resolveClass
(
props
.
getProperty
(
"logImpl"
)
)
;
configuration
.
setLogImpl
(
logImpl
)
;
configuration
.
setConfigurationFactory
(
resolveClass
(
props
.
getProperty
(
"configurationFactory"
)
)
)
;
}
|
因为setting变量直接改变的是Mybatis的行为,所以配置项直接存于Confirguration的属性中。
3、typeAliases配置的解析
进入typeAliasesElement方法,用于对typeAliases配置的解析:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private
void
typeAliasesElement
(
XNode
parent
)
{
if
(
parent
!=
null
)
{
for
(
XNode
child
:
parent
.
getChildren
(
)
)
{
if
(
"package"
.
equals
(
child
.
getName
(
)
)
)
{
String
typeAliasPackage
=
child
.
getStringAttribute
(
"name"
)
;
configuration
.
getTypeAliasRegistry
(
)
.
registerAliases
(
typeAliasPackage
)
;
}
else
{
String
alias
=
child
.
getStringAttribute
(
"alias"
)
;
String
type
=
child
.
getStringAttribute
(
"type"
)
;
try
{
Class
<
?
>
clazz
=
Resources
.
classForName
(
type
)
;
if
(
alias
==
null
)
{
typeAliasRegistry
.
registerAlias
(
clazz
)
;
}
else
{
typeAliasRegistry
.
registerAlias
(
alias
,
clazz
)
;
}
}
catch
(
ClassNotFoundException
e
)
{
throw
new
BuilderException
(
"Error registering typeAlias for '"
+
alias
+
"'. Cause: "
+
e
,
e
)
;
}
}
}
}
}
|
该方法将typeAliases的配置项提取之后,存入了typeAliasRegistry这个对象,该对象是在BaseBuilder中初始化的:
1
2
3
4
5
6
7
8
9
10
|
public
abstract
class
BaseBuilder
{
protected
final
Configuration
configuration
;
protected
final
TypeAliasRegistry
typeAliasRegistry
;
protected
final
TypeHandlerRegistry
typeHandlerRegistry
;
public
BaseBuilder
(
Configuration
configuration
)
{
this
.
configuration
=
configuration
;
this
.
typeAliasRegistry
=
this
.
configuration
.
getTypeAliasRegistry
(
)
;
this
.
typeHandlerRegistry
=
this
.
configuration
.
getTypeHandlerRegistry
(
)
;
}
|
在Configuration类中,我们看到了该对象的声明:
1
|
protected
final
TypeAliasRegistry
typeAliasRegistry
=
new
TypeAliasRegistry
(
)
;
|
打开该类的代码,发现特别简单的,用一个MAP存储了别名和对应的类的映射:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
TypeAliasRegistry
{
private
final
Map
<
String
,
Class
>>
TYPE_ALIASES
=
new
HashMap
<
String
,
Class
>
>
(
)
;
public
TypeAliasRegistry
(
)
{
registerAlias
(
"string"
,
String
.
class
)
;
registerAlias
(
"byte"
,
Byte
.
class
)
;
registerAlias
(
"long"
,
Long
.
class
)
;
registerAlias
(
"short"
,
Short
.
class
)
;
registerAlias
(
"int"
,
Integer
.
class
)
;
registerAlias
(
"integer"
,
Integer
.
class
)
;
registerAlias
(
"double"
,
Double
.
class
)
;
registerAlias
(
"float"
,
Float
.
class
)
;
registerAlias
(
"boolean"
,
Boolean
.
class
)
;
|
在构造函数中Mybatis已经默认注册了一些常用的别名和类的关系,所以我们可以在mappers的xml文件中使用这些短名字。
4、typeHandlers配置元素的解析
mybatis提供了大部分数据类型的typeHandlers,如果我们要定制自己的类型处理器比如实现数据库中0/1两个数字到中文男/女的映射,就可以自己实现typeHandler
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
|
private
void
typeHandlerElement
(
XNode
parent
)
throws
Exception
{
if
(
parent
!=
null
)
{
for
(
XNode
child
:
parent
.
getChildren
(
)
)
{
if
(
"package"
.
equals
(
child
.
getName
(
)
)
)
{
String
typeHandlerPackage
=
child
.
getStringAttribute
(
"name"
)
;
typeHandlerRegistry
.
register
(
typeHandlerPackage
)
;
}
else
{
String
javaTypeName
=
child
.
getStringAttribute
(
"javaType"
)
;
String
jdbcTypeName
=
child
.
getStringAttribute
(
"jdbcType"
)
;
String
handlerTypeName
=
child
.
getStringAttribute
(
"handler"
)
;
Class
>
javaTypeClass
=
resolveClass
(
javaTypeName
)
;
JdbcType
jdbcType
=
resolveJdbcType
(
jdbcTypeName
)
;
Class
>
typeHandlerClass
=
resolveClass
(
handlerTypeName
)
;
if
(
javaTypeClass
!=
null
)
{
if
(
jdbcType
==
null
)
{
typeHandlerRegistry
.
register
(
javaTypeClass
,
typeHandlerClass
)
;
}
else
{
typeHandlerRegistry
.
register
(
javaTypeClass
,
jdbcType
,
typeHandlerClass
)
;
}
}
else
{
typeHandlerRegistry
.
register
(
typeHandlerClass
)
;
}
}
}
}
}
|
在该方法中,通过反射得到了javaTypeClass、jdbcType、typeHandlerClass三个变量,这三个变量组成(javaType、jdbcType、typeHandler)三元组,当遇到javaType到jdbcType的转换,或者遇到jdbcType到javaType的转换时就会使用该typeHandler。
然后该方法调用了TypeHandlerRegistry.register进行注册,TypeHandlerRegistry对象是从BaseBuilder中的Configuration对象中获取的:
1
2
3
4
5
6
7
8
9
10
|
public
abstract
class
BaseBuilder
{
protected
final
Configuration
configuration
;
protected
final
TypeAliasRegistry
typeAliasRegistry
;
protected
final
TypeHandlerRegistry
typeHandlerRegistry
;
public
BaseBuilder
(
Configuration
configuration
)
{
this
.
configuration
=
configuration
;
this
.
typeAliasRegistry
=
this
.
configuration
.
getTypeAliasRegistry
(
)
;
this
.
typeHandlerRegistry
=
this
.
configuration
.
getTypeHandlerRegistry
(
)
;
}
|
在TypeHandlerRegistry中,建立了几个Map映射:
1
2
3
4
5
6
7
|
public
final
class
TypeHandlerRegistry
{
private
final
Map
<
JdbcType
,
TypeHandler
>>
JDBC_TYPE_HANDLER_MAP
=
new
EnumMap
<
JdbcType
,
TypeHandler
>
>
(
JdbcType
.
class
)
;
private
final
Map
<
Type
,
Map
<
JdbcType
,
TypeHandler
>>>
TYPE_HANDLER_MAP
=
new
HashMap
<
Type
,
Map
<
JdbcType
,
TypeHandler
>
>>
(
)
;
private
final
TypeHandler
UNKNOWN_TYPE_HANDLER
=
new
UnknownTypeHandler
(
this
)
;
private
final
Map
<
Class
>
,
TypeHandler
>
>
ALL_TYPE_HANDLERS_MAP
=
new
HashMap
<
Class
>
,
TypeHandler
>
>
(
)
;
|
第一个是JdbcType为key的map,第二个是JavaType为key的map,第三个是未知的处理器、最后一个是包含全部的处理器;
当执行SQL的时候,会将javaBean的JavaType转换到DB的jdbcType,而查询出来数据的时候,又需要将jdbcType转换成javaType,在TypeHandlerRegistry的构造函数中,已经注册好了很多默认的typeHandler,大部分情况下不需要我们添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public
TypeHandlerRegistry
(
)
{
register
(
Boolean
.
class
,
new
BooleanTypeHandler
(
)
)
;
register
(
boolean
.
class
,
new
BooleanTypeHandler
(
)
)
;
register
(
JdbcType
.
BOOLEAN
,
new
BooleanTypeHandler
(
)
)
;
register
(
JdbcType
.
BIT
,
new
BooleanTypeHandler
(
)
)
;
register
(
Byte
.
class
,
new
ByteTypeHandler
(
)
)
;
register
(
byte
.
class
,
new
ByteTypeHandler
(
)
)
;
register
(
JdbcType
.
TINYINT
,
new
ByteTypeHandler
(
)
)
;
register
(
Short
.
class
,
new
ShortTypeHandler
(
)
)
;
register
(
short
.
class
,
new
ShortTypeHandler
(
)
)
;
register
(
JdbcType
.
SMALLINT
,
new
ShortTypeHandler
(
)
)
;
|
要实现一个typeHandler,需要实现接口,该接口提供的就是从javaType到jdbcType的setParameter方法,以及从jdbcType到javaType转换的getResult方法:
1
2
3
4
5
6
7
8
9
10
11
|
public
interface
TypeHandler
void
setParameter
(
PreparedStatement
ps
,
int
i
,
T
parameter
,
JdbcType
jdbcType
)
throws
SQLException
;
T
getResult
(
ResultSet
rs
,
String
columnName
)
throws
SQLException
;
T
getResult
(
ResultSet
rs
,
int
columnIndex
)
throws
SQLException
;
T
getResult
(
CallableStatement
cs
,
int
columnIndex
)
throws
SQLException
;
}
|
5、objectFactory配置项的解析
如果想自己控制查询数据库的结果到JavaBean映射的生成,则可以创建自己的objectFactory,解析代码如下:
1
2
3
4
5
6
7
8
9
|
private
void
objectFactoryElement
(
XNode
context
)
throws
Exception
{
if
(
context
!=
null
)
{
String
type
=
context
.
getStringAttribute
(
"type"
)
;
Properties
properties
=
context
.
getChildrenAsProperties
(
)
;
ObjectFactory
factory
=
(
ObjectFactory
)
resolveClass
(
type
)
.
newInstance
(
)
;
factory
.
setProperties
(
properties
)
;
configuration
.
setObjectFactory
(
factory
)
;
}
}
|
可以看到,该配置项包含type属性,以及properties子节点,创建好ObjectFactory对象后,就会设置到configuration中:
1
2
|
// Configuration对象的objectFactory成员变量
protected
ObjectFactory
objectFactory
=
new
DefaultObjectFactory
(
)
;
|
要实现ObjectFactory,需要继承该接口:
1
2
3
4
5
6
|
public
interface
ObjectFactory
{
void
setProperties
(
Properties
properties
)
;
}
|
该工厂接口提供了设置属性列表,还有创建对象的工厂方法。
6、plugin元素的解析
plugin,即mybatis的插件,可以让我们自己进行开发用于扩展mybatis。
进入pluginElement方法进入解析:
1
2
3
4
5
6
7
8
9
10
11
|
private
void
pluginElement
(
XNode
parent
)
throws
Exception
{
if
(
parent
!=
null
)
{
for
(
XNode
child
:
parent
.
getChildren
(
)
)
{
String
interceptor
=
child
.
getStringAttribute
(
"interceptor"
)
;
Properties
properties
=
child
.
getChildrenAsProperties
(
)
;
Interceptor
interceptorInstance
=
(
Interceptor
)
resolveClass
(
interceptor
)
.
newInstance
(
)
;
interceptorInstance
.
setProperties
(
properties
)
;
configuration
.
addInterceptor
(
interceptorInstance
)
;
}
}
}
|
该段代码,首先获取intercepter元素作为拦截器,然后读取该节点的所有子节点作为配置项,最后调用configuration.addInterceptor方法添加到了configuration中的interceptorChain中,该对象是拦截器链的一个包装对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public
class
InterceptorChain
{
private
final
List
public
Object
pluginAll
(
Object
target
)
{
for
(
Interceptor
interceptor
:
interceptors
)
{
// target变量每次也在变化着
target
=
interceptor
.
plugin
(
target
)
;
}
return
target
;
}
public
void
addInterceptor
(
Interceptor
interceptor
)
{
interceptors
.
add
(
interceptor
)
;
}
public
List
return
Collections
.
unmodifiableList
(
interceptors
)
;
}
}
|
该类中使用List
7、environments的配置解析
environments可以配置多个环境配置,每个配置包含了数据源和事务管理器两项,如下所示代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
void
environmentsElement
(
XNode
context
)
throws
Exception
{
if
(
context
!=
null
)
{
if
(
environment
==
null
)
{
environment
=
context
.
getStringAttribute
(
"default"
)
;
}
for
(
XNode
child
:
context
.
getChildren
(
)
)
{
String
id
=
child
.
getStringAttribute
(
"id"
)
;
if
(
isSpecifiedEnvironment
(
id
)
)
{
TransactionFactory
txFactory
=
transactionManagerElement
(
child
.
evalNode
(
"transactionManager"
)
)
;
DataSourceFactory
dsFactory
=
dataSourceElement
(
child
.
evalNode
(
"dataSource"
)
)
;
DataSource
dataSource
=
dsFactory
.
getDataSource
(
)
;
Environment
.
Builder
environmentBuilder
=
new
Environment
.
Builder
(
id
)
.
transactionFactory
(
txFactory
)
.
dataSource
(
dataSource
)
;
configuration
.
setEnvironment
(
environmentBuilder
.
build
(
)
)
;
}
}
}
}
|
代码中通过isSpecifiedEnvironment方法判断当前的id是不是指定要读取的environment,如果是的话通过反射获取事务管理器和数据源,然后用Environment.Builder创建Enviroment对象并设置到Configuration中,在Configuration中可以看到Enviroment成员变量:
1
|
protected
Environment
environment
;
|
而Enviroment对象也只包含了这三个属性:
1
2
3
4
|
public
final
class
Environment
{
private
final
String
id
;
private
final
TransactionFactory
transactionFactory
;
private
final
DataSource
dataSource
;
|
8、databaseIdProvider配置项的解析
mybatis当然不只是支持mysql,也会支持oracle、sqlserver等不同的数据库,解析代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
void
databaseIdProviderElement
(
XNode
context
)
throws
Exception
{
DatabaseIdProvider
databaseIdProvider
=
null
;
if
(
context
!=
null
)
{
String
type
=
context
.
getStringAttribute
(
"type"
)
;
// awful patch to keep backward compatibility
if
(
"VENDOR"
.
equals
(
type
)
)
{
type
=
"DB_VENDOR"
;
}
Properties
properties
=
context
.
getChildrenAsProperties
(
)
;
databaseIdProvider
=
(
DatabaseIdProvider
)
resolveClass
(
type
)
.
newInstance
(
)
;
databaseIdProvider
.
setProperties
(
properties
)
;
}
Environment
environment
=
configuration
.
getEnvironment
(
)
;
if
(
environment
!=
null
&&
databaseIdProvider
!=
null
)
{
String
databaseId
=
databaseIdProvider
.
getDatabaseId
(
environment
.
getDataSource
(
)
)
;
configuration
.
setDatabaseId
(
databaseId
)
;
}
}
|
解析databaseIdProvider后里面的Properties中存储了各种数据库的映射,并且databaseIdProvider提供了一个根据dataSource获取对应的databseId的方法,以VendorDatabaseIdProvider为例,是通过connection.getMetaData().getDatabaseProductName()获取数据库的产品名称,然后从刚才databaseIdProvider中获取对应的databaseId:
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
|
public
class
VendorDatabaseIdProvider
implements
DatabaseIdProvider
{
private
static
final
Log
log
=
LogFactory
.
getLog
(
VendorDatabaseIdProvider
.
class
)
;
private
Properties
properties
;
@Override
public
String
getDatabaseId
(
DataSource
dataSource
)
{
if
(
dataSource
==
null
)
{
throw
new
NullPointerException
(
"dataSource cannot be null"
)
;
}
try
{
return
getDatabaseName
(
dataSource
)
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Could not get a databaseId from dataSource"
,
e
)
;
}
return
null
;
}
@Override
public
void
setProperties
(
Properties
p
)
{
this
.
properties
=
p
;
}
private
String
getDatabaseName
(
DataSource
dataSource
)
throws
SQLException
{
String
productName
=
getDatabaseProductName
(
dataSource
)
;
if
(
this
.
properties
!=
null
)
{
for
(
Map
.
Entry
<
Object
,
Object
>
property
:
properties
.
entrySet
(
)
)
{
if
(
productName
.
contains
(
(
String
)
property
.
getKey
(
)
)
)
{
return
(
String
)
property
.
getValue
(
)
;
}
}
// no match, return null
return
null
;
}
return
productName
;
}
private
String
getDatabaseProductName
(
DataSource
dataSource
)
throws
SQLException
{
Connection
con
=
null
;
try
{
con
=
dataSource
.
getConnection
(
)
;
DatabaseMetaData
metaData
=
con
.
getMetaData
(
)
;
return
metaData
.
getDatabaseProductName
(
)
;
}
finally
{
if
(
con
!=
null
)
{
try
{
con
.
close
(
)
;
}
catch
(
SQLException
e
)
{
// ignored
}
}
}
}
}
|
在获取了databaseId之后,最后将databaseId设置到configuration,后续当执行SQL的时候会自动根据该databaseId来映射具体数据库的SQL。
9、mappers配置项的解析
mappers的解析最为复杂,我们假设mapper文件均是url指定的xml文件,来进行解析流程的查看:
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
|
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
{
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加载mapper.xml,然后进入parse()方法
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资源文件的地址后,进入了XMLMapperBuilder.parse()方法:
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
|
public
void
parse
(
)
{
if
(
!
configuration
.
isResourceLoaded
(
resource
)
)
{
configurationElement
(
parser
.
evalNode
(
"/mapper"
)
)
;
configuration
.
addLoadedResource
(
resource
)
;
bindMapperForNamespace
(
)
;
}
parsePendingResultMaps
(
)
;
parsePendingChacheRefs
(
)
;
parsePendingStatements
(
)
;
}
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
(
"select|insert|update|delete"
)
)
;
}
catch
(
Exception
e
)
{
throw
new
BuilderException
(
"Error parsing Mapper XML. Cause: "
+
e
,
e
)
;
}
}
|
然后进入了configurationElement(parser.evalNode(“/mapper”));方法后会读取所有xml中mapper下的子元素,在这里我们只查看buildStatementFromContext方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
private
void
buildStatementFromContext
(
List
if
(
configuration
.
getDatabaseId
(
)
!=
null
)
{
buildStatementFromContext
(
list
,
configuration
.
getDatabaseId
(
)
)
;
}
buildStatementFromContext
(
list
,
null
)
;
}
private
void
buildStatementFromContext
(
List
for
(
XNode
context
:
list
)
{
final
XMLStatementBuilder
statementParser
=
new
XMLStatementBuilder
(
configuration
,
builderAssistant
,
context
,
requiredDatabaseId
)
;
try
{
statementParser
.
parseStatementNode
(
)
;
}
catch
(
IncompleteElementException
e
)
{
configuration
.
addIncompleteStatement
(
statementParser
)
;
}
}
}
|
该方法出现了一个XMLStatementBuilder用于select/insert/update/delete语句各自的解析,在XMLStatementBuilder.parseStatementNode方法中解析了各种语句的属性和参数以及动态SQL的处理,最后调用builderAssistant.addMappedStatement方法,所有的参数和内容被构建成MappedStatement,添加到了configuration中:
1
2
|
MappedStatement
statement
=
statementBuilder
.
build
(
)
;
configuration
.
addMappedStatement
(
statement
)
;
|
以下是configuration中的mappedStatements对象:
1
2
3
|
// Configuration的mappedStatements对象
protected
final
Map
<
String
,
MappedStatement
>
mappedStatements
=
new
StrictMap
"Mapped Statements collection"
)
;
|
这里的StrictMap就是一个HashMap,在addMappedStatement方法中可以看到该map的Key是各个SQL的ID:
1
2
3
|
public
void
addMappedStatement
(
MappedStatement
ms
)
{
mappedStatements
.
put
(
ms
.
getId
(
)
,
ms
)
;
}
|
由此可以推测整个Mybatis执行SQL的过程:
- 业务代码调用SqlMapperInterface.method方法;
- Mybatis根据method的全限定名称作为ID,从mappedStatements找到构建好的MappedStatement对象;
- 使用MappedStatement中读取的SQL等各种配置,执行SQL;
Mybatis初始化的对象产出列表总结
以上就是对Mybatis初始化过程的详解,其最终产出了以下对象列表:
- 总产出:SqlSessionFactory,相当于ConnectionFactory用于生产SqlSession,而SqlSession相当于Connection用于实际的SQL查询;
- SQlSessionFactory的核心对象Configuration,所有的Mybatis配置项和Mapper配置列表,都会被解析并读取到该对象的属性中;
- Configuration中的各个对象:
- Configuration.variables,类型为Properties,存储Mybatis配置的Properties对象;
- Configuration.直接属性,setting的配置,因为直接影响Mybatis的行为,直接赋值到了Configuration对象的属性;
- Configuration.TypeAliasRegistry对象,存储别名映射,KEY是别名,VALUE是别名对应的类;
- Configuration.TypeHandlerRegistry对象,存储typeHandler映射,KEY是javaType或者jdbcType,VALUE是typeHandler类;
- Configuration.ObjectFactory对象,存储单个的ObjectFactory对象,用于DB映射JAVABEAN对象的创建;
- Configuration.InterceptorChain对象,存储Plugin对象列表,用户可以添加用于扩展Mybatis;
- Configuration.Environment对象,指定开发/测试/线上环境,根据其dataSource也决定了databaseId对象的取值;
- Configuration.databaseId,存储使用的DB的类型,Mybatis会根据不同的DB做SQL适配;
- Configuration.mappedStatements,存储KEY为ID,VALUE为MappedStatement的MAP,执行SQL时从此处获取对象;
参考资料:
- XML 映射配置文件 http://www.mybatis.org/mybatis-3/zh/configuration.html
- XML 映射文件 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
本文地址:http://www.crazyant.net/2089.html,转载请注明来源。