本文将结合源码,分析mybatis基本的运行原理。导入Idea:参考
MyBatis的解析和运行原理一览图
MyBatis的运行过程分为两大步:
- 读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory;
- SqlSession的执行过程。
MyBatis底层架构的基础掌握:
- 反射技术
- 动态代理技术
1. 构建SqlSessionFactory的过程
构建的两大步:
- 第一步:通过org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件,对出所配置的参数,并将读取的能容存入org.apache.ibatis.session.Configuration类对象中。而Configuration采用的是单例模式,几乎所有的MyBatis配置内容都会存放在这个单例对象中,以便后续将这些内容读出。
- 第二步:使用Configuration对象去创建SqlSessionFactory。MyBatis中的SqlSessionFactory是个接口,而不是一个实现类,为此MyBatis提供了一个默认的实现类org.apache.ibatis.session.default.DefaultSqlSessionFactory。在大部分情况下都没有必要自己创建新的SqlSessionFactory实现类。
这种创建的方式就是一种Builder模式,对于复杂的对象而言,使用构造参数很难实现。这时使用一个类Configuration作为统领,一步步构建所需要的内容,最终获取对象。
1.1 构建Configuation
首先来看看,xml是如何解析的。用XMLConfigBuider解析XML的源码。我们来看XMLConfiguration中的一段源码:
package org.apache.ibatis.builder.xml;
/**inmports 略 **/
public class XMLConfigBuilder extends BaseBuilder {
。。。。。。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
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);
}
}
}
从源码中我们可以看到,它是根据xml的标签节点名称一步步解析内容并得到对应的信息,而这些信息正是我们所配置的内容。那么这些读取的信息都去了哪里呢? 如typeHandlers
的内容,读取后就注册到了TypeHandlerRegistry对象当中。XMLConfigBuilder类还继承了BaseBuilder类。我来看看BaseBuilder的部分源码:
package org.apache.ibatis.builder;
import ......
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();
}
......
看到了,BaseBuilder定义了单例的configuration,而typeHandlerRegistry又是configuration的一个单例。不难想到,我们配置的typeHandlers内容被读取到先注册到了typeHandlerRegistry对象里面,然后最后又到了Configuration对象里。其他的配置也是相同的道理,最后能通过Configuration对象来获取。
1.2 Configuration的作用
在SqlSessionFactory构建中,Configuration是最重要的,它包含如下的作用:
- 读入配置文件,包括基础的配置文件XML和映射器的XML(或注解)。
- 初始化一些基础的配置,比如MyBatis的别名等;一些重要的类对象,如插件、映射器、Object工厂、typeHandlers对象等等。
- 提供单例,为后续创建SessionFactory服务,提供配置的参数。
- 执行一些重要的初始化对象方法。
关于初始化,它会初始化如下的内容:
- properties 全局参数
- typeAliases 别名
- Plugins 插件
- objectFactory 对象工厂
- reflectionFactory 对象包装工厂
- settings 环境设置
- environments 数据库环境
- databaseIdProvider 数据库标识
- typeHandlers 类型转换器
- Mappers 映射器
1.3 构建映射器(Mapper)的内部组成
同样的,当XMLMapperBuilder解析XML时,会将每一个SQL和其配置的内容保存下来,那么他是怎么保存的呢?
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. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
读取完后最后到了这里 MappedStatement:
package org.apache.ibatis.mapping;
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
......
}
着这里我么就能找到我们Mapper里面所配置的东西。其中private SqlSource sqlSource;
是提供BoundSql的地方,它是一个接口,而不是一个实现类。表示从XML文件或注释中读取的映射语句的内容。它创建将从用户接收的输入参数传递到数据库的SQL。
package org.apache.ibatis.mapping;
/**
* Represents the content of a mapped statement read from an XML file or an annotation.
* It creates the SQL that will be passed to the database out of the input parameter received from the user.
*
* @author Clinton Begin
*/
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
BoundSql是一个结果对象:
package org.apache.ibatis.mapping;
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
......
}
2. SqlSessiion运行过程
2.1 映射器(Mapper)的动态代理
先来看看MyBatis是如何实现getMapper方法的:
public class DefaultSqlSession implements SqlSession {
......
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
......
}
我们看到它调用了Configuration对象的getMapper方法:
public class Configuration {
......
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
......
}
它又运用了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);
}
}
首先,它会判断是否注册了一个Mapper,如果没有则会抛出异常信息。如果有,就会启用MapperProxyFactory工厂来生成一个代理实例
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.session.SqlSession;
/**
* @author Lasse Voss
*/
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@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);
}
}
Mapper映射是通过动态代理来的
@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()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
使用了JDK动态代理,先判断是不是类,使得话直接执行invoke方法。但是这里的Mapper是一个接口,所以生成了一个MapperMethod对象,通过cachedMapperMethod方法对其初始化,然后执行execute方法,把SqlSession和当前运行的参数传递过去。
public Object execute(SqlSession sqlSession, Object[] args) {
......
result = executeForMap(sqlSession, args);
......
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
MapperMethod类采用命令模式运行,最终通过SqlSession去运行对象的SQL而已。
总结:到了这里我们就能知道MyBatis为什么只用Mapper接口就可以运行了,因为Mapper的XML文件的命名空间对应的是这个接口的全限定名,而方法就是那条SQL的id,这样MyBatis就可以根据全路径和方法名,将其和代理对象绑定起来。通过动态代理技术,让这个接口运行起来,而后采用命令模式。最后使用SqlSession接口的方法使得它能够执行对应的SQL。
2.2 SqlSession的四大对象
- Executor代表执行器,由它调度StatementHandler、ParameterHandler、ResultSetHandler等来执行对应的SQL。其中StatementHandler是最重要的。
- StatementHandler的作用是使用数据库的Statement(PtreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。
- ParameterHandler是用来处理SQL参数的。
- ResultSetHandler是进行数据集(ResultSet)的封装返回处理的,它相当复杂,好在我们不经常用到它。
Exector-执行器
SqlSession其实只是一个门面,真正干活的是执行器。是一个真正执行Java和数据库交换的对象。有三种执行器(settings元素中的defaultExecutor可配置):
- SIMPLE -简易执行器,没什么特别的,默认的执行器
- REUSE-它是一种能够执行重用预处理语句的执行器。
- BATCH-执行器重用语句和批量更新,批量专用的执行器。
Configuration类中创建执行器:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
判断配置类型确定创建哪种Executor,它的缓存则用CachingExecutor进行包装。最后interceptorChain.pluginAll(executor)
运用插件的相关代码。
我们用SimpleExecutor来看看Exector是如何调度的:
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
configuration.newStatementHandle
还是通过Configuration来创建StatementHandler的。然后使用prepareStatement对Sql编译和参数进行初始化。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
prepareStatement实际进行调用了prepare方法进行预编译与基础设置,然后通过parameterize方法设置参数,最后使用StatementHandler的query方法,把resultHandler传递。
StatementHandler-数据库会话器
同样是Configuration对象生成:
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;
}
new RoutingStatementHandler
很明显,创建的真实对象是RoutingStatementHandler。它实现了StatementHandler接口,用代理对象一层层的封装。
然鹅RoutingStatementHandler却不是真实服务的对象。它是通过适配器模式来找到对应的StatementHandler来执行的。与Excutor一样,RoutingStatementHandler分为三种:
- SimpleStatementHandler 对应JDBC的 Statement
- PreparedStatementHandler 对应JDBC的 PreparedStatement
- CallableStatementHandler 对应JDBC的 CallableStatement
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
它定义了一个适配器-------delegate,适配器的作用就不说明了。接下来看看返回的StatementHandler是怎么执行查询的,以最常用的PreparedStatementHandler为例:
package org.apache.ibatis.executor.statement;
import ......;
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleCursorResultSets(ps);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
}
父类BaseStatementHandler:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
如上,instantiateStatement()方法是对SQL进行了预编译,然后做些基础配置,比如超时、获取的最大行数等的设置。Executor会调用parameterize()方法去设置参数。显然,这个时候设置参数的方法是由ParameterHandler来完成的。然后执行参数和SQL都被prepare()方法预编译了,参数在parameterize()方法中已经设置了,所以只要返回结果就可以了。执行后我们就能看到ResultSetHandler返回。
到这里,MyBatis执行SQL的流程就比较清晰了。
再来看看其他细节
ParameterHandler--参数处理器
使用它来对预编译语句进行参数设定的:
package org.apache.ibatis.executor.parameter;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface ParameterHandler {
Object getParameterObject();//返回参数对象
void setParameters(PreparedStatement ps) throws SQLException; //设置预编译SQL语句的参数
}
具体的实现类:
package org.apache.ibatis.scripting.defaults;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
/**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
分析可知,它还是从parameterObject对象中取到参数,然后使用typeHandler转换,如果没设置,根据签名注册的typeHandler对象参数进行处理。而typeHandler也是在MyBatis初始化时,注册在Configuration里面的,需要时就可以直接拿过来用。
MyBatis就是这样完成参数设置的。
ResultSetHandler----结果处理器
package org.apache.ibatis.executor.resultset;
import org.apache.ibatis.cursor.Cursor;
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}