MyBatis源码解析之核心组件
在此声明,此文章是对江荣波老师的《MyBatis3源码深度解析》的总结,尊重原作者。
MyBatis核心组件
这些组件的作用如下:
- Configuration:用于描述MyBatis的主要配置信息,其他组件需要获取配置信息的时候,直接通过Configuration对象获取,MyBatis在应用启动的时候,将Mapper配置信息、类型别名、TypeHandler等注册到Configuration组件中,其他组件需要这些信息的时候,也可以从这个对象获取。
- MappedStatement:MappedStatement用于描述Mapper中的SQL配置信息,是对Mapper XML配置文件中的<select|update|delete|insert>等标签或者@Select/@Update等注解配置信息的封装。
- SqlSession:SqlSession是MyBatis提供的面向用户的API,表示和数据库的会话对象,用于完成对数据库的操作功能,SqlSssion是Executor组建的外观,目的是对外提供易于理解和使用的数据库操作对象。
- Executor:它是MyBatis的SQL执行器,MyBatis所有对数据库的操作都是Executor完成的。
- StatementHandler:StatementHandler封装了对JDBC Statement对象的操作,比如为Statement对象设置参数,调用Statement接口提供的方法与数据库交互,等等。
- ParameterHandler:为Statement(PrepareStatement、CallableStatement)对象参数占位符设置值。
- ResultSetHandler:封装了对JDBC中的ResultSet对象操作。当我们执行Slelect语句时,它负责帮我们把查询出来的数据转换成Java对象。
- TypeHandler:类型处理器,用于处理JDBC类型和Java类型的映射关系,它的作用在于,我们在为PrepareStatement或者CallableStatement设置参数是,可以根据相应的Java类型找到对应的JDBC类型,而且在将查询结果转换成Java对象时,能根据JDBC类型匹配上Java数据类型。
配置信息类Configuration
MyBatis框架的配置信息有两种:
-
配置MyBatis框架属性的主配置文件,对应着Configuration类里面的属性,通过XPath解析XML文件得到相对应的配置,以下只是几个常用的属性,具体可参考官方文档。
<settings> <!-- 这个配置使全局的映射器启用或禁用二级缓存 --> <setting name="cacheEnabled" value="true" /> <!-- 允许 JDBC 支持生成的键。需要适合的驱动。如果设置为 true 则这个设置强制生成的键被使用, 尽管一些驱动拒绝兼容但仍然有效(比如 Derby) --> <setting name="useGeneratedKeys" value="true" /> <!-- 配置默认的执行器。SIMPLE 执行器没有什么特别之处。REUSE 执行器重用预处理语句。 BATCH 执行器重用语句和批量更新 --> <setting name="defaultExecutorType" value="REUSE" /> <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 --> <setting name="lazyLoadingEnabled" value="false" /> <!-- 设置超时时间,它决定驱动等待一个数据库响应的时间。 --> <setting name="defaultStatementTimeout" value="25000" /> ...... </settings>
-
配置执行SQL语句的Mapper配置文件,就像下面的例子。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.blog4java.mybatis.example.mapper.UserMapper"> <sql id="userAllField"> id,create_time, name, password, phone, nick_name </sql> <select id="listAllUser" resultType="com.blog4java.mybatis.example.entity.UserEntity" > select <include refid="userAllField"/> from user </select> <select id="getUserByEntity" resultType="com.blog4java.mybatis.example.entity.UserEntity"> select <include refid="userAllField"/> from user <where> <if test="id != null"> AND id = #{id} </if> <if test="name != null"> AND name = #{name} </if> <if test="phone != null"> AND phone = #{phone} </if> </where> </select> <select id="getUserByPhone" resultType="com.blog4java.mybatis.example.entity.UserEntity"> select <include refid="userAllField"/> from user where phone = ${phone} </select> </mapper>
Configuration不仅作为一个配置类,还作为一个容器存放了TypeHandler、TypeAlias、Mapper接口以及Mapper SQL配置信息。MyBatis在启动的时候,会对所有的配置进行解析,然后将解析的内容注册到Configuration对应的属性中。
Configuration类中通过以下属性保存TypeHandler、TypeAlias等信息。
//用于注册Mapper接口信息,建立Mapper接口的Class对象和MapperProxyFactory对象
//(MapperProxyFactory用于创建Mapper的代理对象MapperProxy)
//之间的联系,作为键值对注册到mapperRegistry的knownMappers属性中。这里用到了工厂模式。
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
//用于注册MyBatis插件信息,MyBatis插件实际就是一个拦截器,拦截器可以有多个,这里用到了责任链模式。
protected final InterceptorChain interceptorChain = new InterceptorChain();
//用于注册所有的TypeHandler
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
//用于注册所有的类型别名
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
//用于注册LanguageDriver,LanguageDriver用于解析SQL配置,将配置信息转换为SqlSource对象。
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//将所有的MappedStatement对象注册到该属性,key为Mapper的Id,Value为MappedStatement对象
protected final Map<String, MappedStatement> mappedStatements =
new StrictMap<MappedStatement>("Mapped Statements collection");
//用于注册Mapper中配置的所有缓存信息,key为Cache的Id,也就是Mapper的命名空间,value为Cache对象。
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
//用于注册Mapper配置文件中通过<ResultMap>标签配置的ResultMap信息,
//ResultMap用于建立Java实体属性与数据库字段之间的映射关系,
//key为ResultMap的Id,该Id是由mapper的命名空间和<ResultMap>标签的id属性组成的,value为解析的ResultMap对象。
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
//用于注册Mapper中通过<ParamterMap>标签解析的参数映射信息,
//key为ParamterMap的Id,该Id是由mapper的命名空间和<ResultMap>标签的id属性组成的,value为ParamterMap对象
protected final Map<String, ParameterMap> parameterMaps =
new StrictMap<ParameterMap>("Parameter Maps collection");
//用于注册KeyGenerator,KeyGenerator是MyBatis的主键生成器。
protected final Map<String, KeyGenerator> keyGenerators =
new StrictMap<KeyGenerator>("Key Generators collection");
//用于注册所以Mapper XML配置文件路径
protected final Set<String> loadedResources = new HashSet<String>();
//用于注册Mapper中通<sql>标签配置的SQL片段,key为这段SQL片段的id,value为XNode对象。
protected final Map<String, XNode> sqlFragments =
new StrictMap<XNode>("XML fragments parsed from previous mappers");
除此之外,Configuration组件还作为Executor、StatementHandler、ParameterHandler组件的工厂类,用于创建这些组件的实例:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang()
.createParameterHandler(mappedStatement, parameterObject, boundSql);
// 执行拦截器链的拦截逻辑
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement,
RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler =
new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 执行拦截器链的拦截逻辑
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler =
new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 执行拦截器链的拦截逻辑
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据executor类型创建对象的Executor对象
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 执行拦截器链的拦截逻辑
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这些工厂方法会根据不同的配置创建对应的实现类,例如如果我们把defaultExecutorType属性设置为REUSE,那么newExecutor返回的是ReuseExecutor实例。设置为Simple,返回的就是SimpleExecutor实例。这就是典型的工厂方法模式的应用。
SQL配置信息MappedStatement
MyBatis3通过MappedStatement描述<select|update|delete|insert>等标签或者@Select/@Update等注解配置的SQL信息。
MappedStatement通过以下这些属性保存<select|update|delete|insert>标签的属性信息。
//在命名空间的唯一标识符
private String id;
//指定SQL执行后返回的最大行数
private Integer fetchSize;
//等待数据库返回请求结果的秒数,超出抛出异常
private Integer timeout;
//Statement的类型,默认为PrepareStatement
private StatementType statementType;
//设置ResultSet对象的特征
private ResultSetType resultSetType;
//废弃
private ParameterMap parameterMap;
//<resultMap>标签配置的实体属性和数据库字段之间建立的结果集映射。
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
//是否使用二级缓存,默认为true
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
//指定LanguageDriver实现。
private LanguageDriver lang;
//适用于<update|insert>,用于将数据库自增主键的值返回填充到实体的属性中。
private String[] keyProperties;
private String[] keyColumns;
private String databaseId;
//这个设置仅对多结果集的情况适用。
private String[] resultSets;
除此之外,MappedStatement类还有一些其他的属性。
private Cache cache; // 二级缓存实例
private SqlSource sqlSource; // 解析SQL语句生成的SqlSource实例
private String resource; // Mapper资源路径
private Configuration configuration; // Configuration对象的引用
private KeyGenerator keyGenerator; // 默认为Jdbc3KeyGenerator,即数据库自增主键,当配置了<selectKey>时,使用SelectKeyGenerator
private boolean hasNestedResultMaps; // 是否有嵌套的ResultMap
private Log statementLog; // 输出日志
SQL执行器Executor
Executor是真正执行SQL操作的组件,它定义了增速删改查的方法,Executor有以下几种不同的实现类。
这里用到的几种设计模式:
- 模板方法模式:我们阅读源码可以发现,在BaseExecutor中定义的方法执行流程以及通用的处理逻辑,具体的方法由子类来实现。
- 享元模式:ReuseExecutor对JDBC中的Statement对象做了缓存,当执行相同的SQL语句时,直接从缓存中提取出Statement对象进行复用,避免了频繁的创建和销毁Statement对象。
- 装饰器模式:当MyBatis开启了二级缓存功能时,会使用CachingExecutor对BaseExecutor的子类进行装饰,为查询操作增加了二级缓存功能。
Executor和数据库交互需要Mapper配置信息,因此Executor需要一个MappedStatement对象作为参数。MyBatis在应用启动的时候,会解析所有的Mapper配置,并将他们注册到Configuration组件中,我们可以调用Configuration对象的getMappedStatement()方法获取对用的MappedStatement对象,之后根据SQL类型调用Executor对象的方法。
操作数据库的类StatementHandler
StatementHandler组件封装了对JDBC Statement的操作,例如设置Statement对象的fetchSize属性、设置查询超时时间的等。StatementHandler有以下几种不同的实现类。
这里用到的几种设计模式:
- 模板方法模式:SimpleStatmentHandler继承自StatementHandler,封装了对JDBC Statement对象t的操作;PreparedStatementHandler封装了对JDBC PreparedStatement对象的操作;而CallableStatementHandler封装了对JDBC CallableStatement对象t的操作。
- 工厂方法模式:RoutingStatementHandler会根据Mapper配置中的statementType属性创建对应的StatementHandler实例。
StatementHandler接口中定义了以下方法:
public interface StatementHandler {
//该方法用于创建JDBC Statemen对象,并完成Statement对象的属性设置。
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
//该方法使用ParameterHandler组件为Statement参数占位符设置值。
void parameterize(Statement statement) throws SQLException;
//将SQL命令添加到批量处理执行列表中。
void batch(Statement statement) throws SQLException;
//调用Statement对象的execute()方法执行更新(包含UPDATE,INSERT,DELETE)语句。
int update(Statement statement) throws SQLException;
//执行查询语句,并使用ResultSetHabdler处理查询的结果集。
<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
//带游标的查询,返回Cursor对象,能够通过迭代动态的从数据库中加载数据,适用于查询数据量较大的情况,避免将所有的数据加载到内存。
<E> Cursor<E> queryCursor(Statement statement) throws SQLException;
//获取Mapper中配置的SQL信息,BondSQL封装了动态SQL解析后的SQL文本和参数映射信息。
BoundSql getBoundSql();
//获取ParameterHandler实例。
ParameterHandler getParameterHandler();
}
参数设置类ParameterHandler
当使用PreparedStatement或者CallableStatement对象时,如果SQL语句中有参数占位符,在执行SQL语句之前,就需要为参数占位符设置值。ParameterHandler的作用是在PreparedStatementHandler和CallableStatementHandler操作对应Statement执行数据库交互之前为参数占位符设置值。ParameterHandler只有一个实现类,即DefaultParameterHandler。
ParameterHandler接口只有两个方法:
public interface ParameterHandler {
//该方法用于获取执行Mapper时传入的参数。
Object getParameterObject();
//该方法用于为PreparedStatement或者CallableStatement对象设置参数值。
void setParameters(PreparedStatement ps) throws SQLException;
}
查询结果转换类ResultSetHandler
ResultSetHandler用于在StatementHandler对象执行完查询操作或存储过程之后,对结果集或存储过程的执行结果进行处理。ResultSetHandler只有一个实现类,那就是DefaultResultSetHandler。
ResultSetHandler接口定义如下:
public interface ResultSetHandler {
//获取Statement对象中的ResultSet对象,对ResultSet对象进行处理,返回包含结果实体的List对象。
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
//将ResultSet对象包装成Cursor对象,对Cursor进行遍历时,能动态的从数据库查询数据。
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
//处理存储过程调用结果
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
类型处理器TypeHandler
TypeHandler是为了解决JDBC类型和Java类型之间的转换而产生的一个组件。BaseTypeHandler实现了TypeHandler,对调用setParamter()方法,参数为Null的情况做了通用的处理。对调用getResult()方法,从ResultSet对象中获取列值出现的异常作了处理。因此我们需要自定义TypeHandler时,只需要继承BaseTypeHandler就行。
TypeHandler定义的接口如下:
public interface TypeHandler<T> {
//为PreparedStatement或者CallableStatement对象设置参数
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;
}
mybatis通过TypeHandlerRegistry建立JDBC类型、Java类型与TypeHandler之间的映射关系。通过Map保存JDBC类型、Java类型与TypeHandler之间的关系,在TypeHandlerRegistry的构造方法中,通过regist()方法注册所有的TypeHandler。
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
......
}
当我们自定义TypeHandler后,也可以调用TypeHandlerRegistry的regist()方法进行注册。