MyBatis详解6.MyBatis技术内幕
点击进入我的博客
MyBatis详解1.概述
MyBatis详解2.MyBatis使用入门
MyBatis详解3.MyBatis配置详解
MyBatis详解4.映射器Mapper
MyBatis详解5.动态SQL
MyBatis详解6.MyBatis技术内幕
MyBatis详解7.插件
MyBatis详解8.集成Spring
1 构建SqlSessionFactory的过程
通过SqlSessionFactoryBuilder构建SqlSessionFactory共有两步:
- 通过XMLConfigBuilder解析MyBatis的配置文件,并读取到Confinguration对象中。
- 使用Confinguration对象去创建SqlSessionFactory,由于SqlSessionFactory是一个接口,最终构建出的其实是DefaultSqlSessionFactory的对象。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 通过XMLConfigBuilder读入配置
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
// ......
}
}
public SqlSessionFactory build(Configuration config) {
// 返回的是DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
Configuration的主要作用如下:
- 读入配置文件,包括基础配置的XML文件和映射器的XML文件。
- 初始化基础配置,比如MyBatis的别名等,一些重要的类对象如,插件、映射器、 ObjectFactory和typeHandler对象。
- 提供单例,为后续创建SessionFactory服务并提供配置的参数。
- 执行一些重要的对象方法,初始化配置信息。
2 映射器的内部构成
一个映射器是由3个部分组成
- MappedStatement,它保存映射器的一个节点(select、insert、delete、update)。包括许多我们配置的SQL、SQL的id、缓存信息、resultMap、parameterType、resultType、languageDriver等重要配置内容。
- SqlSource,是一个接口,它的主要作用是根据参数和其他的规则组装SQL,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
- BoundSql,它是建立SQL和参数的地方。它有3个常用的属性:SQL、parameterObject、parameterMappings。
BoundSql的主要属性
- BoundSql会提供3个主要的属性:parameterObject、parameterMappings和sql
- parameterObject为参数本身,可以传递简单对象、POJO、Map或者@Param注解的参数。
- parameterMappings,它是一个List,每一个元素都是ParameterMapping的对象。这个对象会描述我们的参数,包括属性、名称、表达式、 javaType、 jdbcType、typeHandler等重要信息。通过它可以实现参数和SQL的结合,以便PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,使得程序准确运行。
- sql属性就是我们书写在映射器里面的一条SQL,在插件的情况下,我们可以根据需要进行改写。
parameterObject的细节
- 传递简单对象(如基本数据类型)时,例如当我们传递int类型时,MyBatis会把参数变为Integer对象传递。如果我们传递的是POJO或者Map,那么这个parameterObject就是你传入的POJO或者Map不变。
- 当然我们也可以传递多个参数,如果没有@Param注解,那么MyBatis就会把parameterObject变为一个Map<String, Object>对象,其键值的关系是按顺序来规划的,类似于这样的形式:
{"1":p1; "2":p2, "3":p3,...,"param1":pl, "param2":p2, "param3":p3}
,可以使用#{param1}或者#{1}去引用第1个参数。 - 如果我们使用@Param注解,那么MyBatis就会把parameterObject会变为一个Map<String, Object>对象,键为@Param注解的键。
3 SqlSession的运行过程
3.1 Mapper的动态代理
我们自定义的Mapper接口想要发挥功能,必须有具体的实现类,在MyBatis中是通过为Mapper每个接口提供一个动态代理类来实现的。整个过程主要有三个类:MapperProxyFactory、MapperProxy、MapperMethod。
- MapperProxyFactory就是MapperProxy的工厂类,主要方法就是包装了Java动态代理的Proxy.newProxyInstance()方法。
- MapperProxy就是一个动态代理类,它实现了InvocationHandler接口。对于代理对象的调用都会被代理到InvocationHandler#invoke方法上。
- MapperMethod包含了具体增删改查方法的实现逻辑。
public class MapperProxyFactory<T> {
// 这里可以看到是通过Java的动态代理来实现的,具体代理的方法被放到来MapperProxy中
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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
// 实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 对代理类的所有方法的执行,都会进入到invoke方法中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 此处判断是否是Object类的方法,如toString()、clone(),如果是则直接执行不进行代理
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 如果不是Object类的方法,则初始化一个MapperMethod并放入缓存中
// 或者从缓存中取出之前的MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用MapperMethod执行对应
return mapperMethod.execute(sqlSession, args);
}
}
public class MapperMethod {
// MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中
// 实际上它最后就是通过SqlSession对象去运行对象的SQL。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: { //...
}
case UPDATE: { //...
}
case DELETE: { //...
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
//...
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
}
3.2 SqlSession中的对象
Mapper执行的过程是通过Executor、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回的:
- Executor代表执行器,由它来调度StatementHandler、ParameterHandler、ResultHandler等来执行对应的SQL。
- StatementHandler的作用是使用数据库的Statement(PreparedStatement)执行操作,起到承上启下的作用。
- ParameterHandler用于SQL对参数的处理。
- ResultHandler是进行最后数据集(ResultSet)的封装返回处理的。
3.3 执行器Executor
执行器是一个真正执行Java和数据库交互的类,一共有三种执行器,我们可以在MyBatis的配置文件中设置defaultExecutorType属性进行选择。
- SIMPLE(
org.apache.ibatis.executor.SimpleExecutor
),简易执行器,默认执行器。 - REUSE(
org.apache.ibatis.executor.ReuseExecutor
),是一种执行器重用预处理语句。 - BATCH(
org.apache.ibatis.executor.BatchExecutor
),执行器重用语句和批量更新,它是针对批量专用的执行器。
// Configure类中创建Executor的具体逻辑
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完成创建之后,会通过interceptorChain来添加插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
创建Executor的具体逻辑在Configure类中,可以看到,在Executor创建完成之后,会通过interceptorChain来添加插件,通过代理到方式,在调度真实的Executor方法之前执行插件代码来完成功能。
Executor的具体执行逻辑
我们通过SimpleExecutor来看一下Executor的具体执行逻辑:
- 根据Configuration来构建StatementHandler
- 然后使用prepareStatement方法,对SQL编译并对参数进行初始化
- 在prepareStatement方法中,调用了StatementHandler的prepared进行了预编译和基础设置,然后通过StatementHandler的parameterize来设置参数并执行。
- 包装好的Statement通过StatementHandler来执行,并把结果传递给resultHandler。
public class SimpleExecutor extends BaseExecutor {
@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();
// (1)根据 Configuration来构建Statementhandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
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;
}
}
3.4 数据库会话器StatementHandler
StatementHandler就是专门处理数据库会话的,创建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;
}
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());
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
}
很显然创建的真实对象是一个RoutingStatementHandler对象,它实现了接口StatementHandler。从RoutingStatementHandler的构造方法来看,它其实是使用来委派模式来把具体的StatementHandler类型隐藏起来,通过RoutingStatementHandler来统一管理。一共用三种具体的StatementHandler类型:SimpleHandler、PreparedStatementHandler、CallableStatementHandler。
通过StatementHandler看执行细节
在Executor的具体执行逻辑中,我们主要关注StatementHandler的prepared、parameterize两个方法。
public abstract class BaseStatementHandler implements StatementHandler {
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// instantiateStatement对SQL进行了预编译
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);
}
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
// 调用parameterize去设置参数,可以发现是通过parameterHandler来具体执行的
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
// 具体的查询就是通过PreparedStatement#execute来执行的
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
}
3.5 参数处理器ParameterHandler
MyBatis是通过ParameterHandler对预编译的语句进行参数设置的。
public interface ParameterHandler {
// 返回参数对象
Object getParameterObject();
// 设置预编译的SQL语句的参数
void setParameters(PreparedStatement ps) throws SQLException;
}
public class DefaultParameterHandler implements ParameterHandler {
@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 e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
MyBatis为ParameterHandler提供了一个实现类DefaultParameterHandler,具体执行过程还是从 parameterObject对象中取参数然后使用typeHandler进行参数处理,而typeHandler也是在My Batis初始化的时候,注册在Configuration里面的,我们需要的时候可以直接拿来用。
3.6 ResultSetHandler
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;
}
MyBatis为我们提供了一个DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的。这个类JAVASSIST或者CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回。