MyBatis的运行过程分为两大步:第1步,读取配置文件缓存到Configuration对象,用以
创建SqlSessionFactory;第2步,SqlSession的执行过程。
最重要的功能是提供MyBatis的核心接口SqlSession
采用Builder模式去创建SqlSessionFactory,在实际中可以通过SqlSessionFactoryBuilder去构建,其构建分为两步:
Configuration是提供XMLConfigBuilder去构建的,首先读出所以的XML配置的信息,然后把它们解析并保存在Configuration单例中。
一般而言,在MyBatis中一条SQL与它相关的配置是由3个部分组成的,它们分别是MappedStatement,
SqlSource和BoundSql。
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class)
MyBatis源码是如何实现getMapper方法的:
public T getMapper(Class type) {
return this.configuration.getMapper(type, this);
}
显然运用到了Configuration对象的getMapper方法,追踪这个方法:
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
运用了映射器的注册器MapperRegistry来获取对应的接口对象,如下:
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
首先判断是否注册一个Mapper,如果没有则会抛出异常;如果有,就会启用MapperProxyFactory工厂来生成一个代理实例,为此再追踪代码:
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return this.mapperInterface;
}
public Map getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
Mapper映射是通过动态代理来实现的。这里可以看到动态代理对接口的绑定,它的作用是生成
动态代理对象(占位)。而代理的方法则放到了MapperProxy类中。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
可以看到这里的invoke方法逻辑。如果Mapper是一个JDK动态代理对象,那么它就会运行到invoke方法里面。invoke首先判断是否是一个类,这里的Mapper是一个接口不是类,所以判定失败,然后生成MapperMethod对象,通过cachedMapperMethod方法来初始化。最后执行execute方法,把SqlSession和当前运行的参数传递进去。
execute源码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
至此,相信大家都知道MyBatis为什么只用Mapper接口便能够运行了。
因为Mapper的XML文件的命名空间对应的是这个接口的全限定名,而方法就是那条SQL的id,这样MyBatis就可以根据全路径和方法名,将其和代理对象绑定起来。通过动态代理技术,让这个接口运行起来起来,而后采用命令模式。最后使用SqlSession接口的方法使得它能够执行对应的SQL。只是有了这层封装,就可以采用接口编程这样的编程更为简单明了。
SqlSession其实是门面,真正干活的是执行器,它是真正执行Java和数据库交互的对象。
MyBatis是怎么创建Executor的,这段代码在Configuration类中:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object 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 (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
MyBatis将根据配置类型去确定需要创建哪一种Executor,它的缓存则用CachingExecutor进行包装Executor。
以SimpleExecutor的query方法作为例子进行讲解,代码如下:
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
int var6;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var6 = handler.update(stmt);
} finally {
this.closeStatement(stmt);
}
return var6;
}
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
protected Cursor doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
stmt.closeOnCompletion();
return handler.queryCursor(stmt);
}
public List doFlushStatements(boolean isRollback) {
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
MyBatis根据Configuration来构建StatementHandler,然后使用prepareStatement
方法,对SQL编译和参数进行初始化。实现过程:它调用StatementHandler的prepare(),然后通过StatementHandler的parameterize()来设置参数,最后使用StatementHandler的query方法,把ResultHandler传递进去,使用它组织结果返回给调用者来完成一次查询,这样焦点又转移到StatementHandler对象上。
数据库会话器专门处理数据库会话的,MyBatis生成StatementHandler的代码如下:
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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口StatementHandler。
和Executor一样,用代理对象做一层层封装。
RoutingStatementHandler不是真实的服务对象,它是通过适配器模式来找到对应的StatementHandler
来执行。在MyBatis中,Executor样,RoutingStatementHandler分为三种:SimpleStatementHandler、
PreparedStatementHandler和CallableStatementHandler。它所对应的是
JDBC的Statement、PreparedStatement(预编译处理)和CallableStatement(存储过程处理)。
在初始化RoutingStatementHandler对象时,会根据上下文环境决定创建哪个具体的StatementHandler
对象实例,代码如下:
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch(ms.getStatementType()) {
case STATEMENT:
this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
根据配置来适配对应的StatementHandler对象。以最常用的PreparedStatementHandler
为例,看看MyBatis是怎么执行查询的。Executor执行查询时会执行StatementHandler
的prepare、parameterize和query方法,其中PreparedStatementHandler的prepare
方法的代码如下:
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(this.boundSql.getSql());
Statement statement = null;
try {
statement = this.instantiateStatement(connection);
this.setStatementTimeout(statement, transactionTimeout);
this.setFetchSize(statement);
return statement;
} catch (SQLException var5) {
this.closeStatement(statement);
throw var5;
} catch (Exception var6) {
this.closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + var6, var6);
}
}
instantiateStatement()方法是对SQL进行了预编译,然后做一些基础配置,比如超时、获取的最大行数等的设置。Executor中会调用它的parameterize()方法去设置参数,如代码:
public void parameterize(Statement statement) throws SQLException {
this.parameterHandler.setParameters((PreparedStatement)statement);
}
这个时候它是调用ParameterHandler去完成。下节再来讨论它是如何实现的,这里先看查询的方法–执行SQL返回的结果,代码如下:
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
在执行前参数和SQL都被prepare()方法预编译,参数在parameterize()方法中已经进行
了设置,所以只要执行SQL然后返回结果就可以了。执行之后我们看到了ResultSetHandler对结果的封装和返回。
MyBatis是通过ParameterHandler对预编译语句进行参数设置的,它的作用是完成对预编译参数的设置,接口定义如下:
public interface ParameterHandler {
//返回参数对象
Object getParameterObject();
//设置预编译SQL语句的参数
void setParameters(PreparedStatement var1) throws SQLException;
}
具体实现就不贴代码了,大致的思路是:从parameterObject对象中取到参数,然后使用typeHandler转换参数,如果有设置,那么它会根据签名注册的typeHandler对参数进行处理。而typeHandler也是在MyBatis初始化时,注册在Configuration里面的,需要时就可以直接拿来用了,MyBatis就是这样完成参数设置的。
ResultSetHandler是组装结果集返回的,ResultSetHandler的接口定义如下:
public interface ResultSetHandler {
//包装结果集
List handleResultSets(Statement var1) throws SQLException;
Cursor handleCursorResultSets(Statement var1) throws SQLException;
void handleOutputParameters(CallableStatement var1) throws SQLException;
}
SqlSession是通过执行器Executor调度StatementHandler来运行的。而StatementHandler经过3步:
其实,parameterize是调用parameterHandler的方法设置的,而参数是根据类型处理器
typeHandler处理的。query/update方法通过ResultSetHandler进行处理结果的封装,
如果是update语句,就返回整数,否则就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。