回顾
StatementHandler
StatementHandler接口是MyBatis的核心接口之一,它完成了MyBatis中最核心的工作,也就是如何去与使用原生JDBC数据库进行交互,是Executor接口的基础,Executor接口也是核心的接口之一
StatementHandler的功能
- 创建对应的Statement对象
- 为SQL绑定实参
- 执行各种SQL语句
- 批量执行SQL语句
- 将结果集映射到结果对象
说白了其实就是组合前面我们研究过的各种模块功能,比如创建Statement对象需要的SQL、实参与占位符的映射是交由前面DynamicSqlSourc或者RawSqlSource;将结果集映射到结果对象,就是上一篇学到的DefaultResultSetHandler负责处理
其是一个接口,来看一下其各种方法
- batch:批量执行SQL
- getBoundSql:获取绑定的SQL
- getParameterHandler:获取参数处理器
- prepare:一些Statement的初始化,比如超时时间、fetchSize
- update:执行更改语句,也就是增删改
- query:执行查询语句
可以看到其实现类有两个
-
BaseStatementHandler:抽象类,提供了一些StatementHandler的共性实现,其实就是提供了参数绑定的方法,其有3个子类,对应具体执行的SQL类型
- PreparedStatementHandler:用于执行预处理SQL的
- CallableStatementHandler:用于执行过过程函数的
- SimpleStatementHandler:用于执行不需要预处理的SQL的
-
RoutingStatementHandler:采用了装饰者模式,用来装饰增强上面PreparedStatementHandler、CallableStatementHandler和SimpleStatementHandler的,虽然从代码上没看到任何增强作用,里面的方法都是直接调用组装的statementHandler方法的
RoutingStatementHandler
成员属性只有一个delegate,就是装饰的StatementHandler,在构造方法上,会根据MappedStatement的StatementType来装饰对应的StatementHandler;前面已经研究过,MappedStatement对应的就是一个SQL节点,也就是select、insert、delete、update节点
然后其实现StatementHandler接口的方法都是直接调用装饰的StatementHandler的,并没有看到一些增强作用,个人觉得可能是MyBatis作者觉得这样便于以后扩展吧,不过有人认为这个RoutingStatementHandler是一个代理类,也有人觉得RoutingStatementHandler使用的是策略模式,关于RoutingStatementHandler究竟作用是什么,后面我们在讨论
BaseStatementHandler
BaseStatementHandler是一个实现了StatementHandler接口的抽象类,实现了绑定参数、生成主键和prepare初始化Statement等方法,其他具体的细节都是交由子类去实现的
成员属性有如下
-
configuration:MyBatis的配置
-
objectFactory:创建实例
-
typeHandlerRegistry:类型转换器注册中心
-
ResultSetHandler:结果集映射处理器
-
ParameterHandler:用于给SQL绑定实参的
-
executor:执行SQL的executor对象
-
mappedStatement:需要执行的SQL节点的解析对象,里面不仅有SQL,还有对应的ResultMap
-
rowBounds:分页
-
BoundSql:绑定的SQL
构造方法
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//从MappedStatement中获取Configuration,个人感觉这个Configuration好乱呀
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
//从Configuration中获取类型转换器注册中心
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
//获取ObjectFactory,用来创建实例
this.objectFactory = configuration.getObjectFactory();
//使用MappedStatement获取绑定的SQL
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
//创建ParameterHandler和ResultSetHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
BaseStatementHandler比较关键的其实就是prepare方法,用来初始化Statement的
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//创建对应的Statement,延迟到子类去实现
statement = instantiateStatement(connection);
//初始化Statement超时时间
setStatementTimeout(statement, transactionTimeout);
//初始化Statement的fetSuze
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);
}
}
没了,所以BaseStaementHandler仅仅提供初始化Statement的prepare方法,而具体创建对应类型的Statement的instantiateStatement方法则是延迟到子类去实现,这也是模板方法的一个体现
下面需要认识一下BaseStatementHandler的两个重要对象
- ParameterHandler:用于参数绑定的
- ResultSetHandler:前面已经认识过了,用来完成结果集映射的
ParameterHandler
前面解析SQL节点时,对于DynamicSqlSource和RawSqlSource都是可能包含问号占位符,而每个问号占位符都是对应两个SqlSource里面的ParameterMappings集合里面的一个元素, 并且是按顺序对应的,但此时仅仅存在映射关系,还没有具体绑定!
具体绑定参数其实就是指对应符号位去注入参数!像以前使用原生JDBC的时候,使用PrepareStatement时,也是要对应占位符来进行注入参数才能够执行SQL,而具体绑定参数的动作是交由ParameterHandler来负责执行的
可以看到,ParameterHandler是一个接口,里面有两个方法
- getParameterObject:获取注入的Java实参对象
- setParameters:给对应的PrepareStatement注入参数
而MyBatis对于ParameterHandler仅仅只有DefaultParameterHandler实现类
DefaultParameterHandler
成员属性有如下
- TypeHandlerRegistry:类型转换注册中心
- MappedStatement:对应的解析SQL节点生成的MappedStatement
- ParameterObject:注入的实参
- Configuration:配置
比较重要的方法是setParameters,也就是DefaultParameterHandler是如何给预处理SQL绑定参数的
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取ParameterMappings
//前面提到过ParameterMapping里面就是#{}占位符里面的内容!
//对应一个#{}占位符
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//判空
if (parameterMappings != null) {
//遍历ParameterMappings集合
for (int i = 0; i < parameterMappings.size(); i++) {
//当前的ParameterMapping
ParameterMapping parameterMapping = parameterMappings.get(i);
//如果不是输出参数
//存储过程才会有输出参数
if (parameterMapping.getMode() != ParameterMode.OUT) {
//value用于记录当前ParameterMapping对应的实参
Object value;
//获取${}里面的token
//需要使用token来获取实参的
//从传进来的java对象中,根据token取出对象里面的对应的属性作为实参
String propertyName = parameterMapping.getProperty();
//判断有没有额外的参数
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
}
//如果传进来的参数对象为空,那就直接为null
else if (parameterObject == null) {
value = null;
}
//判断类型转换注册中心有没有该参数对象的类型转换
//如果有,其实就代表是基础类型了,不需要进行映射解析了
//比如Integer、Long、Double那些
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
}
//如果参数对象不为空,并且不是基本类型、或者对应的包装类
//代表是一个封装起来的对象,就需要进行解析
else {
//获取参数对象的MetaObject对象
MetaObject metaObject = configuration.newMetaObject(parameterObject);
//从MetaObject对象中获取token对应的成员属性
//MetaObject获取token对应的成员属性是使用get方法来完成的
//因为参数对象必须要有get方法
value = metaObject.getValue(propertyName);
}
//获取${}占位符里面的type属性对应的TypeHandler
//TypeHandler用来类型转换,Java转为JDBC
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//获取${}占位符里面的jdbc属性对应的JdbcType
JdbcType jdbcType = parameterMapping.getJdbcType();
//但一般我们都不会去设置这两个属性,一般都是${xxx}完事
//因此需要完成自动映射
if (value == null && jdbcType == null) {
//从配置文件中获取jdbcType为Null时的JDBC类型
//是一个OTHER类型
//OTHER类型表明jdbcType直接特定于数据库对应的类型
//就是数据库中是什么类型,jdbcType就是什么类型
//是JDBC提供的一种类型
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//使用TypeHandler来给PrepareStatement对应占位符位置注入实参
//因为PrepareStatement实参与占位符绑定的位置是从1开始,因此要加1
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
现在大概清楚了DefaultParameterHandler是如何工作的
- 获取前面解析SQL的#{}占位符时的ParameterMappings集合
- 遍历ParameterMappings集合
- 判断传进来的参数对象是否为空,如果为空,那么实参也为Null
- 判断传进来的参数对象是不是已经注册在类型转换中心了,如果已经注册,代表参数对象是基础类型,也就是基本数据类型、包装类、String和日期等类型,参数对象本身就是实参,不需要继续解析其成员属性
- 如果参数对象不为空,而且不是基础类型,那么代表参数对象是一个自定义的引用类型,真正的实参是参数对象内部的成员属性,将参数对象转换为MetaObject
- 取出#{}占位符里面的token,参数对象的MetaObject通过token来调用参数对象的get方法,从而获取占位符对应的实参
- 然后会根据ParameterMapping对象里面的JdbcType和TypeHandler
- JdbcType就是确认的JDBC类型,如果前面#{}没有配置的话,会为OTHER类型
- 使用TypeHandler来给PrepareStatement按照当前位置去注入实参
认识完ParameterHandler之后,具体看BaseStatementHandler的子类
SimpleStatementHandler
SimpleStatementHandler是用来执行一些普通不需要注入参数的SQL的,也就是使用Statement来完成数据库操作,所以SQL中是不能有占位符的,也就是不可以预处理
创建Statement
BaseStatementHandler已经提供了prepare模板方法去初始化对应的Statement了,然后延迟了创建Statment的具体方法到子类去实现,也就是instantiateStatement方法
可以看到,SimpleStatementHandler只是简单地使用Connection去创建了Statement出来
执行查询
可以看到,执行查询的方法就是获取SQL了之后,就直接调用Statement的execute方法,然后交由ResultSetHandler去处理结果集映射
执行修改
@Override
public int update(Statement statement) throws SQLException {
//获取SQL
String sql = boundSql.getSql();
//获取注入的实参
//因为
Object parameterObject = boundSql.getParameterObject();
//获取主键生成器
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
//使用主键生成器
if (keyGenerator instanceof Jdbc3KeyGenerator) {
//执行SQL
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
//MySQL返回的添加结果行数
rows = statement.getUpdateCount();
//将生成的主键注入进实参中,这里是数据库生成的主键
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
//执行SQL
statement.execute(sql);
//MySQL返回添加的结果行数
rows = statement.getUpdateCount();
//将生成的主键注入进实参中,这里是SlectKey节点中的SQL生成的主键
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
//如果没有主键生成器,直接执行
statement.execute(sql);
rows = statement.getUpdateCount();
}
//返回MySQL的结果
return rows;
}
可以看到,SimpleStatement除了执行SQL之外,还要去处理主键的生成,执行完SQL后,将生成的主键注入进实参中!从而获取到主键
- 使用Statement执行插入SQL
- 使用KeyGenerator生成主键,调用后处理方法,将主键注入进实参中
PrepareStatementHandler
PrepareStatementHandler与SimpleStatement的较大不同之处在于PrepareStatementHandler是基于PrepareStatement接口完成数据库交互的;而SimpleStatement是基于Statement接口来完成交互的;说白了就是一个可以预处理,另外一个不能预处理
创建Statement
同样,先去看看其是如何创建Statement的
protected Statement instantiateStatement(Connection connection) throws SQLException {
//获取绑定的SQL
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
//创建PrepareStatement
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
//创建PrepareStatement
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);
}
}
-
获取绑定的SQL
-
使用绑定的SQL去创建PrepareStatement
-
而前面的SimpleStatementHandler,则是创建Statement,然后只有在执行SQL的时候才回去绑定SQL
执行查询
可以看到,关于查询的方法也是直接调用PrepareStatement的execute方法去执行,然后使用ResultSetHandler来进行处理结果集映射而已
执行修改
public int update(Statement statement) throws SQLException {
//执行SQL
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;
}
可以看到,执行修改的动作跟SimpleStatement其实是很类似的
- 执行SQL
- 获取修改的行数
- 获取实参对象
- 获取主键的生成器
- 往实参对象注入生成的主键
- 返回修改的行数
可能有人觉得,PrepareStatement不是还要绑定参数的吗?!
其实PrepareStatementHandler提供了绑定参数的方法,但PrepareStatementHandler自身并没有去绑定,而是由上层的Executor接口进行绑定!
绑定实参
可以看到,绑定实参就是调用ParameterHandler去给PrepareStatement去绑定实参,具体的细节上面已经提到过
CallableStatementHandler
CallableStatementHandler是用来执行存储过程的,底层依赖CallableStatement去完成数据库交互的功能
具体的细节就不看了,比较少用函数过程