Mybatis源码5 StatementHandler ,ParameterHandler
Mybatis5 StatementHandler ,ParameterHandler
一丶概述
前面我们总结了SqlSession--->CachingExecutor--->BaseExector---->Excutor子类 doQuery,doUpdate的执行流程,mybatis操作数据库总归是基于JDBC的,再和数据库打交道之前必定需要构建statement并且把参数映射到sql语句上,然后请求数据库,然后从Result中拿到数据映射到java 对象上。Excutor执行这些操作其实是调用下面三大对象完成的:
- StatementHandler:作用是使用数据库的Statement(默认使用PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。
- ParameterHandler:是用来处理SQL参数的。
- ResultSetHandler:是进行数据集(ResultSet)的封装返回处理的
执行的流程如下
二丶Excutor是如何得到应该使用的StamentHandler的
1.Exectuor准备StatmentHandler并且调用的逻辑
- SimpleExecutor
当真正需要请求数据库时,从Configuration大管家中创造一个新的StatementHandler(ReuseExecutor 使用了Map缓存sql和对应的StatmentHanler 并不一定会去new 一个StatementHandler)
接着准备调用prepareStatement
主要是获取连接(这个获取的连接被mybatis进行了动态代理,提供了打印日志的功能)然后调用StamentHandler的prepare方法创建一个Statment并且调用StamentHandler的parameterize 将参数映射到sql中,后面就是执行器调用Statment的 query or update 方法操作数据
-
ReuseExecutor
大体逻辑和SimpleExector相同,准备Statment 的逻辑不一样
从缓存map中根据sql 拿Statment 这是ReuseExecutor 为什么叫 ReuseExecutor的原因,但是不是拿到Statment即可,还要确保statement的数据库连接没有关闭了(这里个人觉得有点瑕疵,既然数据库连接关闭了,而且这个map中的Statement也不会被继续使用了,为什么不将这个Statement也关闭)
-
BatchExecutor
对应StatmentHandler的操作和SimpleExecutor一样,只是BatchExecutor的doUpdate方法会调用StatmentHandler的batch方法,而且会使用list存储每一个Statement和对应的BachResult,调用doFlushStatements会进行批量提交
三丶StatementHandler和Configuration创建StatementHandler
1.StatemenHanlder接口
定义了StatementHandler的基本功能
//准备语句 子类可以实现返回不同的Statement子类
Statement prepare(Connection connection)
throws SQLException;
//参数化
void parameterize(Statement statement)
throws SQLException;
//批处理
void batch(Statement statement)
throws SQLException;
//update
int update(Statement statement)
throws SQLException;
//select-->结果给ResultHandler
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
//得到绑定sql
BoundSql getBoundSql();
//得到参数处理器
ParameterHandler getParameterHandler();
2.BaseStatementHandler
模板方法设计模式,提取公共的操作到父类,具体不同的地方抽成抽象方法交由子类实现
-
prepare
-
设置超时时长 setStatementTimeout
优先选择mapperstatement中的超时时长(对xml TimeOut指定的内容)后选择Configuration中指定的超时时长,根本是调用 stmt.setQueryTimeout
-
设置读取条数 setFetchSize
获取mappedStatement中设置的FetchSize设置(起始我们如果数据库数据过多可以设置最多获取多少条,避免oom)根本是调用stmt.setFetchSize
-
3.RoutingStatementHandler
主要是适配多个StatmentHandler的实现,有点装饰器,有点工厂,又有点适配器的意思
后续具体方法的实现都是调用delegate对应的方法,相当于RoutingStatementHandler 只是做了一个根据MappedStatement中的StatementType配置创建不同的StatmentHandler
4.PrepareStatementHandler
默认的StatementHandler
- instantiateStatement
-
为什么新增更新删除可以返回影响的行数
本质是调用了PreparedStatement.getUpdateCount 返回
-
parameterize 调用parameterHandler 进行参数映射
-
query调用 resultSetHandler的handleResultSets进行参数映射
5.SimpleStatementHandler
- instantiateStatement
connection.createStatement生成Statement
6.CallableStatementHandler
-
instantiateStatement
connection.prepareCall 生成Statement
-
update 和 query方法会调用resultSetHandler 处理存储过程的输出参数
-
parameterize相比PrepareStatementHandler多了将存储过程的输出参数注册到 CallableStatement
7.Configuration创建StatementHandler
默认创建的是RoutingStatementHandler 或许我们可以把 RoutingStatementHandler 构造方法内,根据StatementType船舰不同实现的逻辑移到这里也是一样的
interceptorChain.pluginAll 使用责任链和动态代理对每一个StatementHandler进行动态代理
四丶ParameterHandler
- ParameterHandler接口定义了如何获取参数,和如何如何设置参数的两个方法
- DefaultParameterHandler 是ParameterHandler接口的唯一实现
1.DefaultParameterHandler 是如何设置参数的
//设置参数
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//1.获取sql语句的参数,ParameterMapping里面包含参数的名称类型等详细信息,还包括类型处理器
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//2.遍历依次处理
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//3.OUT类型参数不处理
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//4.获取参数名称
String propertyName = parameterMapping.getProperty();
//5.如果propertyName是动态参数,就会从动态参数中取值。(当使用<foreach>的时候,MyBatis会自动生成额外的动态参数)
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//6.如果参数是null,不管属性名是什么,都会返回null。
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//7.判断类型处理器是否有参数类型,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。
value = parameterObject;
} else {
//8.这种情况下是复杂对象或者Map类型,通过反射方便的取值。通过MetaObject操作
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//9.获取对应的数据库类型
JdbcType jdbcType = parameterMapping.getJdbcType();
//空类型
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//10.对PreparedStatement的占位符设置值(类型处理器可以给PreparedStatement设值)
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);
}
}
}
}
}