Mybatis源码分析--初相识
Mybatis版本:3.5.6
本人的GitHub地址:https://github.com/KeminaPera/mybatis-3
分支名称:study/mybatis-3.5.6
个人建议:将Mybatis的源码fork到个人仓库,按照官网文档编写demo,然后调试跟踪
包结构
包名称 | package-info.java内容 | 描述 |
---|---|---|
annotations | The MyBatis data mapper framework makes it easier to use a relational database with object-oriented applications. | Mybatis提供的注解 |
binding | Bings mapper interfaces with mapped statements. | 将Mapper接口内的方法和MappedStatement进行绑定 |
builder | Base package for the Configuration building code. | 用来构建Mybatis"大总管"--Configuration类,支持xml和annotation两种 |
cache | Base package for caching stuff. | Mybatis缓存 |
cursor | Base package for cursor feature. | 流式查询相关 |
datasource | Base package for Datasources. | Mybatis内置的数据源 |
exception | Base package for exceptions. | Muybatis框架定义的异常 |
executor | Contains the statement executors. | 执行器<重点> |
io | Utilities to read resources. | 用来读取资源的工具包,对classloader的包装 |
jdbc | Utilities for JDBC. | JDBC相关工具包 |
lang | Java Language Package. | 与java语言版本相关 |
logging | Base package for logging. | 集成第三方日志框架 |
mapping | Base package for mapping. | 用于结果集的映射 |
parsing | Parsing utils. | 对XPath的封装,对解析xml提供基础功能 |
plugin | Base package for handing plugins. | Plugins对外暴露 |
reflection | Reflection utils. | 对java反射的封装 |
scripting | Base package for languages. | 动态sql支持 |
session | Base package. Contains the SqlSession. | sqlSession相关 |
transaction | Base package for transactions. | Mybatis对事务的封装 |
type | Type handlers. | 类型转化器 |
-
Binding模块:在调用
SqlSession
相应方法执行数据库操作时,需要制定映射文件中定义的 SQL 节点,如果 SQL 中出现了拼写错误,那就只能在运行时才能发现。为了能尽早发现这种错误,Mybatis 通过 Binding 模块将用户自定义的Mapper 接口与映射文件关联起来,系统可以通过调用自定义 Mapper 接口中的方法执行相应的 SQL 语句完成数据库操作,从而避免上述问题。注意,在开发中,我们只是创建了 Mapper 接口,而并没有编写实现类,这是因为 Mybatis 自动为 Mapper 接口创建了动态代理对象。 -
缓存模块:Mybatis提供了
一级缓存
和二级缓存
,这两级缓存都是在该模块实现的。注意事项:这两级缓存默认是与Mybatis
以及整个应用在同一JVM中运行,需要共享同一块内存;如果需要缓存大量数据,则需要考虑分布式缓存(Redis等)。 -
数据源模块:Mybatis内部提供了3种数据源的实现,同时也提供了与第三方数据源集成的接口。数据源是开发中的常用组件之一,很多开源的数据源都提供了丰富的功能,如连接池、检测连接状态等,选择性能优秀的数据源组件,对于提供ORM 框架以及整个应用的性能都是非常重要的。
-
资源加载模块(io):该模块主要封装了类加载器,确定了类加载器的使用顺序,并提供了加载类文件以及其他资源文件的功能。
-
日志模块:该日志模块的主要功能就是集成第三方日志框架。例如Log4j、Log4j2、slf4j等。
-
解析器模块:该模块主要有2个功能:1)封装了Xpath,为Mybatis初始化时解析mybatis-config.xml配置文件以及映射配置文件提供支持;2)为处理动态SQL语句中的占位符提供支持。
-
拦截器/插件模块:该模块提供了对
Mybatis
四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)增强的API,是给开发人员自行开发的接口,可以对四大对象内部的方法进行拦截,例如分页插件等。 -
反射模块:Mybatis的反射模块对Java反射进行了很好的封装,提供了简易的API,方便上层调用,并且对反射操作进行了一系列优化。比如,缓存了类的元数据
MetaClass
和对象的元数据MetaObject
,提高了反射操作的性能。 -
事务管理模块:一般地,Mybatis 与 Spring 框架集成,由 Spring 框架管理事务。但 Mybatis 自身对数据库事务进行了抽象,提供了相应的事务接口和简单实现。
-
类型转换模块(type):该模块的主要功能是实现JAVA类型与JDBC类型的相互转换,在SQL语句绑定时,会将java类型转换成JDBC类型;在映射结果集时,会将数据由JDBC类型转换成Java类型。
配置文件
Mybatis中有2大配置文件,分别是Mybtis配置文件和Mapper配置文件。
Configuration XML
该配置文件中定义的设置和属性会影响Mybatis行为,配置文件结构如下:
properties
属性定义,这些属性可以引用外部的属性且可动态配置。即可以在项目的其他属性文件中配置然后通过properties
标签的resource
或url
属性引入这些外部属性,亦可通过properties
标签的子标签property
定义属性。
<properties resource="引入的外部属性文件路径" url="引入的外部属性url">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
properties标签中引入或定义的属性会在下面Mybatis配置文件中使用,一般这些属性用来替换需要动态配置的值。例如下面数据源的配置:
<dataSource type="POOLED">
<!-- 使用外部引入的属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<!-- 使用properties标签中定义的属性 -->
<property name="username" value="${username}"/>
<!-- 如果password属性不存在,用户密码会变成123456 -->
<property name="password" value="${password:123456}"/>
</dataSource>
settings
Mybatis中极其重要的配置,他们会改变Mybatis的运行时行为。 官网给出了settings
标签下所有的配置项,我们例举常用的配置项
属性 | 描述 |
---|---|
cacheEnabled | 全局开启或关闭配置文件中所有映射器配置的任何缓存 |
lazyLoadingEnabled | 全局开启或关闭懒加载,当开启时,所有关联对象都会延迟加载 |
useGeneratedKeys | 允许JDBC支持自动生成主键,需要驱动支持。如果设置为true则这个设置强制使用自动生成主键 |
defaultExecutorType | 配置默认的执行器。默认是SIMPLE(普通执行器);除此之外还有REUSE(会重用预处理语句)和BATCH(重用语句且批量更新) |
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="defaultExecutorType" value="REUSE"/>
</settings>
typeAliases
类型别名,是为Java类型起一个简短的名字。只和XML配置有关。
直接指定类的别名
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>
通过指定包路径
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
typeHandlers
类型处理器,主要负责Java类型与JDBC类型的相互转换。在预处理语句(PrepareStatement)设置参数时,将java类型转为JDBC类型;在处理结果集时,将JDBC类型转换成Java类型。Mybatis在org.apache.ibatis.type
包下已经内置了很多TypeHandler
,TypeHandler
顶级抽象如下:
public interface TypeHandler<T> {
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;
}
可以重写类型处理器或者创建自己的类型处理器来处理不支持或者非标准的类型。一般我们会选择继承TypeHandler
的基本实现类BaseTypeHandler
来自定义类型处理器。
objectFactory
对象工厂,Mybatis每次创建结果的新实例时,都会使用对象工厂实例来完成。默认的对象工厂(DefaultObjectFactory)需要做的仅仅是实例化目标类。要么通过默认构造方法,要么当参数映射存在时通过带参构造方法实例化。如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
}
public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
}
public void setProperties(Properties properties) {
super.setProperties(properties);
}
public <T> boolean isCollection(Class<T> type) {
return Collection.class.isAssignableFrom(type);
}
}
在xml配置文件中配置此工厂
<objectFactory type="org.mybatis.example.ExampleObjectFactory">
<property name="someProperty" value="100"/>
</objectFactory>
plugins
插件开发,插件开发是 MyBatis 设计人员给开发人员留给自行开发的接口,MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。MyBatis 允许使用插件来拦截的方法调用包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler 接口,这几个接口也是 MyBatis 中非常重要的接口,我们下面会详细介绍这几个接口。
environment
MyBatis 环境配置,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。 这里注意一点,虽然 environments 可以指定多个环境,但是SqlSessionFactory 只能有一个,为了指定创建哪种环境,只要将它作为可选的参数传递给
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
databaseIdProvidor
数据库厂商标示,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId
属性
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver"/>
<property name="DB2" value="db2"/>
<property name="Oracle" value="oracle" />
</databaseIdProvider>
mapper
映射器,告诉 MyBatis 去哪里找到这些 SQL 语句,mappers 映射配置有四种方式
<!-- Using classpath relative resources -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- Using url fully qualified paths -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- Using mapper interface classes -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- Register all interfaces in a package as mappers -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
上面的一个个属性都对应着一个解析方法,都是使用 XPath 把标签进行解析,解析完成后返回一个 DefaultSqlSessionFactory
对象,它是 SqlSessionFactory 的默认实现类。这就是 SqlSessionFactoryBuilder 的初始化流程,通过流程我们可以看到,初始化流程就是对一个个 /configuration
标签下子标签的解析过程。
Mapper XML
该配置文件主要使用来定义SQL的,也是Mybatis真正强大的地方,大大节省了JDBC的代码。包含的一级元素如下:
select
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>
select标签还可以指定属性,官网已经详细介绍,这里只介绍常用的几个属性:
属性 | 描述 |
---|---|
id | 用来标记一个SQL语句,必须指定,在相同namespace中必须唯一 |
parameterType | 传入该语句的参数的全限定类名 |
resultType | 该条语句返回结果的全限定类名,如果设置了别名,可以指定为该别名 |
resultMap | 引用外部的resuleMap ,与resultType 属性只能二选其一 |
useCache | 是否开启在Mybatis二级缓存中缓存该语句的结果 |
statementType | 指定当前语句的类型,STATEMENT 、PREPARED 、CALLABLE 三选其一,默认是PREPARED |
flushCache | 该语句执行时是否刷新一级和二级缓存,select语句默认是false |
insert、update and delete
<insert id="insertAuthor">
insert into Author (id,username,password,email,bio)
values (#{id},#{username},#{password},#{email},#{bio})
</insert>
<update id="updateAuthor">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>
<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>
属性 | 描述 |
---|---|
flushCache | 执行该语句时是否刷新一级和二级缓存,默认insert、update、delete语句是false |
useGeneratedKeys | (insert and update only)告诉Mybatis使用JDBC的getGeneratedKeys 方法从数据库获取主键,默认是false |
keyProperty | (insert and update only) |
keyColumn | (insert and update only) |
sql
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
主要目标是复用sql的代码片段,使用<sql>标签定义一个代码片段
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
在sql语句的适当位置使用<include>标签引入由refid
引用的sql片段
Parameters
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
Result Maps(重点)
id & result
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
id
和result
标签都是用来配置结果集列与对象属性的映射关系,他们唯一不同的是id
用于标识列,而result
用于普通列。
constructor
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>
这个不是很重要,主要是告诉Mybatis
这些参数麻烦使用构造器的方式注入
association
association
元素是处理has-one
关系,例如Blog
持有一个Author
对象
public class Blog {
private Integer id;
private String name;
private LocalDateTime createTime;
// 关联
private Author author;
}
Nested Select for Association
<resultMap id="blogResult" type="Blog">
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
存在两条查询语句,一条查询Author
,一条查询Blog
,其中Blog的resultMap关联selectAuthor
去查询Blog的author
属性。
存在问题:
上面这种方式会出现N+1问题,一条语句执行查询返回结果,然后对返回结果中的每条记录再执行一条语句来查询关联对象的数据,所以当查询出的数据量比较大时会出现性能问题。
Nested Results for Association
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from Blog B left outer join Author A on B.author_id = A.id
where B.id = #{id}
</select>
使用连表查询,一次直接将所需结果查出来
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" resultMap="authorResult" />
</resultMap>
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>
上面resultMap
主要是告诉Mybatis
如何对结果集解析封装
collection
collection
标签是用来处理has-many这种关系的,一个Teacher
会管理多名Student
对象
public class Teacher {
private Integer id;
private String name;
private List<Student> students;
}
Nested Select for Collection
<resultMap id="teacherResult" type="Teacher">
<collection property="students" javaType="ArrayList" column="id" ofType="Student" select="selectStudentForTeacher"/>
</resultMap>
<select id="selectTeacher" resultMap="teacherResult">
SELECT * FROM TEACHER WHERE ID = #{id}
</select>
<select id="selectStudentForTeacher" resultType="Student">
SELECT * FROM STUDENT WHERE TEACHER_ID = #{id}
</select>
和上面Association
使用很类似,不同的是resultMap
标签中association
标签换成了collection
标签;javaType
不再是具体实体类型,而是变成了集合类型;多了一个ofType
标签,由该标签来指定集合内部的元素类型
Nested Results for Collection
<resultMap id="teacherResult" type="Teacher">
<id property="id" column="teacher_id" />
<result property="name" column="teacher_name"/>
<collection property="students" ofType="Student" resultMap="teacherStudentsResult" columnPrefix="student_"/>
</resultMap>
<resultMap id="teacherStudentsResult" type="Student">
<id property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
同Association
,不再过多介绍
Auto-mapping
-
NONE
- disables auto-mapping. Only manually mapped properties will be set. -
PARTIAL
- will auto-map results except those that have nested result mappings defined inside (joins). -
FULL
- auto-maps everything.
cache
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
定义缓存的失效策略,刷新缓存的间隔以及缓存大小等等,此处的缓存是二级缓存,作用范围是namespace
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
用于不同namespace之间共享缓存
核心组件
执行流程
组件介绍
-
SqlSession:是
Mybatis
对外暴露的核心API,提供了对数据库的DRUD操作接口。 -
Executor:执行器,由SqlSession调用,负责数据库操作以及Mybatis两级缓存的维护
-
StatementHandler:封装了
JDBC Statement
操作,负责对Statement
的操作,例如PrepareStatement
参数的设置以及结果集的处理。 -
ParameterHandler:是StatementHandler内部一个组件,主要负责对
ParameterStatement
参数的设置 -
ResultSetHandler:是StatementHandler内部一个组件,主要负责对
ResultSet
结果集的处理,封装成目标对象返回 -
TypeHandler:用于Java类型与JDBC类型之间的数据转换,
ParameterHandler
和ResultSetHandler
会分别使用到它的类型转换功能 -
MappedStatement:是对Mapper配置文件或Mapper接口方法上通过注解申明SQL的封装
-
Configuration:Mybatis所有配置都统一由
Configuration
进行管理,内部由具体对象分别管理各自的小功能模块
小结
本篇只是对Mybatis的源码模块的划分、模块提供的功能以及对应包的管理有了初步认识;要想使用Mybatis,肯定是需要对Mybatis进行一定的配置,这里也分别介绍了Mybatis核心配置文件和Mapper配置文件的具体配置项;除此之外,还对Mybatis的执行流程以及核心组件进行简单介绍,这里只需要记住大致流程,后续会对每个组件逐一进行详细分析。