1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 4.[源码]mybatis之JDBC处理器(一)StatementHandler与ParamHandler

4.[源码]mybatis之JDBC处理器(一)StatementHandler与ParamHandler

时间:2019-04-05 16:16:32

相关推荐

4.[源码]mybatis之JDBC处理器(一)StatementHandler与ParamHandler

我们都知道,mybatis是一款基于JDBC的持久层框架。而之前Mybatis多级缓存文章中,我们学习了会话、执行器相关的知识,都没有涉及到JDBC,那么它到底在哪里呢?不错,所有JDBC相关操作都在我们今天的主角——StatementHandler之中。

StatementHandler负责处理Mybatis中与JDBC相关的逻辑。一次sql请求,会经过会话,然后是执行器,再之后是由StatementHandler执行jdbc最终到达数据库。本篇文章中,我们就要了解StatementHandler执行逻辑。

NO.1|StatementHandler的基本方法

StatementHandler是一个接口,我们先来了解一下它定义的基本方法。

public interface StatementHandler:// 创建StatementStatement prepare(Connection connection, Integer transactionTimeout) throws SQLException;// 设置参数void parameterize(Statement statement) throws SQLException;void batch(Statement statement) throws SQLException;int update(Statement statement) throws SQLException;<E> List<E> query(Statement statement, ResultHandler resultHandler)throws SQLException;<E> Cursor<E> queryCursor(Statement statement) throws SQLException;// 获取BoundSqlBoundSql getBoundSql();ParameterHandler getParameterHandler();

介绍其中几个重点方法:

1.Statement prepare:创建一个Statement对象,即该方法会通过Connection对象创建Statement对象。

2.void parameterize:为Statement设置参数

3.List query:查询操作

4.int update:更新操作

5.BoundSql getBoundSql:获取BoundSql(可以被jdbc识别的sql语句)

NO.2|StatementHandler的结构

我们在学习JDBC的时候就了解过,JDBC中的Statement就是负责与数据库进行交互的对象,而且有三种类型可选。所以负责封装JDBC的StatementHandler自然而然的存在三种类型的具体实现,与JDBC的Statement三种类型相互呼应,来实现不同Statement的不同功能。它们分别是:

SimpleStatementHandler(对应JDBC中常用的Statement接口,用于简单SQL的处理);

PreparedStatementHandler(对应JDBC中的PreparedStatement,预编译SQL的接口);

CallableStatementHandler(对应JDBC中CallableStatement,用于执行存储过程相关的接口)。

除了这些不同的个性方法,不同的statementHandler具体实现在执行操作的过程中还是有一些共性的方法可以抽取出来,比如closeStatement()、getBoundSql(),所以我们又需要一个类来承载这些共性的功能方法,于是便有了BaseStatementHandler,这是一个抽象类,实现了StatementHandler接口。刚刚提到的三个具体实现(SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler)直接继承于BaseStatementHandler,而没有直接实现StatementHandler这个顶级接口。

说到这里不知道大家是不是有点眼熟,有没有想到我们之前讲过的Executor的继承体系,共性的方法放在BaseExecutor中,个性的方法放在不同的实现类中。没错,这个思想就是在框架中使用频率颇高的一种设计模式——模板方法,这里再跟新盆友介绍一下模板方法的定义。

TIPS:模板模式(Template Pattern), 一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。

StatementHandler体系中,除了以上提到的,还有一个类需要被注意—RoutingStatementHandler。它也实现了StatementHandler接口,但是与上面介绍的类不同,它其实没有具体实现什么功能,仅仅是提供了一种路由的能力。即在它的构造方法中根据传入的MappedStatement的statementType,来判定创建哪种具体实现类,并持有这个实现类( 属性:StatementHandler delegate)。在通过它执行方法的时候,它只需要调用持有的StatementHandler实现类的对应方法,由StatementHandler 实现类来具体执行。(在下面查看源码看流程的时候会把RoutingStatementHandler放入其中串联起来。这里要先知道它的功能。)

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());}@Overridepublic Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {return delegate.prepare(connection, transactionTimeout);}@Overridepublic void parameterize(Statement statement) throws SQLException {delegate.parameterize(statement);}

所以我们的StatementHandlerd的结构体系如下图所示:

NO.3|StatementHandler预处理流程

通过之前的学习,我们已经知道,查询先会经过会话,执行器。而具体的执行器实现类(比如simpleExecutor)执行时,在缓存没有命中的情况下,最终会调用doQuery()方法,这个方法将会创建StatementHandler对象,并利用StatementHandler操作JDBC与数据库进行交互。

在doQuery()方法中首先会通过MappedStatement获取Configuration对象,然后调用configuration的newStatementHandler()方法来创建RoutingStatementHandler。上面介绍过,RoutingStatementHandler在构造时,会起到路由的作用——根据传入的MappedStatement的statementType来选择具体创建哪个的StatementHandler实现类,并把这个实现类赋值给自己成员变量StatementHandler delegate。所以configuration.newStatementHandler()方法返回的虽然是RoutingStatementHandler,但由于其持有了StatementHandler delegate从而具备了jdbc操作的能力。

SimpleExecutor:public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {// 获取Configuration对象Configuration configuration = ms.getConfiguration();// 创建RoutingStatementHandler,用来处理Statement// RoutingStatementHandler类中初始化delegate类(SimpleStatementHandler、PreparedStatementHandler)StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds,resultHandler, boundSql);// 子流程1:设置参数stmt = prepareStatement(handler, ms.getStatementLog());// 子流程2:执行SQL语句(已经设置过参数),并且映射结果集return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}

Configuration:public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// 创建路由功能的StatementHandler——根据MappedStatement中的StatementTypeStatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}

从以上doQuery()方法的源码可以看出,在创建好StatementHandler之后,就要利用StatementHandler进行操作了。共分两步进行,分别是:

子流程1:创建statement,并为其设置参数。

子流程2:执行sql语句,映射结果集。(这个内容下一篇会讲哒,不要急)

今天先为大家介绍子流程1的执行逻辑:创建statement,并为其设置参数这个部分 。

doQuery()会调用SimpleExecutor的prepareStatement()方法来完成这个流程。在prepareStatement()方法中。先创建连接,然后调用StatementHandler的prepare()方法创建statement,最后调用StatementHandler的parameterize()方法为statement进行SQL参数设置。

SimpleExecutor:private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 获取连接Connection connection = getConnection(statementLog);// 创建Statement(PreparedStatement、Statement、CallableStatement)stmt = handler.prepare(connection, transaction.getTimeout());// SQL参数设置handler.parameterize(stmt);return stmt;}

下面要对创建statement和设置参数两个部分分别进行讲解。

1.创建statement

因为我们使用最多,并且也是默认的statement处理器是prepareStatementHandler,所以我们这里在涉及statementHandler具体实现类的源码部分也用prepareStatementHandler来展示。

先来看handler.prepare()方法,这里调用的是RoutingStatementHandler的prepare()方法,RoutingStatementHandler的prepare()方法中,会通过持有StatementHandler具体实现类(比如:prepareStatementHandler)来调用prepare()方法。

由于这个方法存在一些共性的功能,所以是在prepareStatementHandler的父类BaseStatementHandler类中实现。prepare()方法中会调用抽象的instantiateStatement()方法来实例化Statement,在创建好Statement后,利用它设置超时时间,设置每次获取条数。

BaseStatementHandler:public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {// 实例化Statement,比如PreparedStatementstatement = 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);}}

BaseStatementHandler:protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

instantiateStatement()方法是一个抽象方法,抽象方法的具体实现自然是由StatementHandler的实现类来完成,比如prepareStatementHandler。所以PreparedStatementHandler类中的instantiateStatement()方法真正创建了statement。

在PreparedStatementHandler类中的instantiateStatement()方法中,先调用boundSql.getSql()来获取带有占位符的SQL语句。这个占位符就是“?”,得到的SQL语句已经是可以被JDBC解析的SQL语句了。这部分的逻辑涉及到解析mapper.xml创建mappedStatement的过程,会在讲这个的时候进行展开,这里仅暂作了解。

然后调用connection.prepareStatement(sql)来创建statement,看到这里就发现了JDBC的痕迹了。

PreparedStatementHandler:@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {// 获取带有占位符的SQL语句String sql = boundSql.getSql();// 处理带有主键返回的SQLif (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) {//看这里, 创建statementreturn connection.prepareStatement(sql);} else {return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}}

2.设置参数

SimpleExecutor的prepareStatement()中创建了statement的部分完成了,下面就要利用statement来设置参数了——handler.parameterize(stmt)。

这里调用了statementHandler的具体实现类的(比如PreparedStatementHandler)的parameterize()方法,在parameterize()方法中,利用ParameterHandler来处理参数。至此,我们的参数处理器——ParameterHandler登场了。

PreparedStatementHandler:@Overridepublic void parameterize(Statement statement) throws SQLException {// 通过ParameterHandler处理参数parameterHandler.setParameters((PreparedStatement) statement);}

ParameterHandler只有一个实现类 DefaultParameterHandler , 它实现了这两个方法。

getParameterObject:用于读取参数

setParameters: 用于对 PreparedStatement 的参数赋值

ParameterHandler对象是在创建StatementHandler对象的同时被创建出来的,由Configuration对象负责。当然这里创建出的是它的实现类—DefaultParameterHandler。

BaseStatementHandler:protected final ParameterHandler parameterHandler;protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {//其他代码略//创建参数处理器this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);}

所以在调用 parameterHandler.setParameters()方法时,调用的是DefaultParameterHandler的setParameters()方法。在这个方法中,先从boundSql中获取ParameterMapping集合,ParameterMapping里面封装了参数映射信息,比如参数名称、类型处理器,ParameterMapping是在处理sql中#{}时处理保存的,是SQL语句中#{}代表的参数信息。(涉及mapper.xml封装过程,到时候会展开讲)。

遍历ParameterMapping集合,也就是遍历每一个需要的参数信息。先获取每个参数名称。利用MetaObject类(MetaObject是mybatis自己封装的底层类,有查找属性、获取属性、设置属性的功能)与 parameterObject(入参对象)通过参数名称获取到当前参数的属性值。再拿到parameterMapping封装的类型处理器。

有了属性值与类型处理器,以及当前parameterMapping在集合中的序号,就可以给PreparedStatement设置参数了。类型处理器(比如:IntegerTypeHandler)会通过PreparedStatement的API去设置非空参数,比如——ps.setInt(i, parameter)。

1DefaultParameterHandler:2@Override3public void setParameters(PreparedStatement ps) {4 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());5 // 获取要设置的参数映射信息(就是对 #{} 里面参数的封装)6 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();7 if (parameterMappings != null) {8 for (int i = 0; i < parameterMappings.size(); i++) {9ParameterMapping parameterMapping = parameterMappings.get(i);10// 只处理入参11if (parameterMapping.getMode() != ParameterMode.OUT) {12Object value;13// 获取属性名称14String propertyName = parameterMapping.getProperty();15if (boundSql.hasAdditionalParameter(propertyName)) {// issue #448 ask first for additional params16 value = boundSql.getAdditionalParameter(propertyName);17} else if (parameterObject == null) {18 value = null;19} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {20 value = parameterObject;21} else {22 MetaObject metaObject = configuration.newMetaObject(parameterObject);23 // 获取每个参数的值24 value = metaObject.getValue(propertyName);25}26// 获取每个参数的类型处理器,去设置入参和获取返回值27TypeHandler typeHandler = parameterMapping.getTypeHandler();28// 获取每个参数的JdbcType29JdbcType jdbcType = parameterMapping.getJdbcType();30if (value == null && jdbcType == null) {31 jdbcType = configuration.getJdbcTypeForNull();32}33try {34 // 给PreparedStatement设置参数35 typeHandler.setParameter(ps, i + 1, value, jdbcType);36} catch (TypeException e) {37 throw new TypeException(38 "Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);39} catch (SQLException e) {40 throw new TypeException(41 "Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);42}43}44 }45 }46}

1public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T>:2 @Override3 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {4 if (parameter == null) {5 if (jdbcType == null) {6throw new TypeException(7 "JDBC requires that the JdbcType must be specified for all nullable parameters.");8 }9 try {10ps.setNull(i, jdbcType.TYPE_CODE);11 } catch (SQLException e) {12throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "13 + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "14 + "Cause: " + e, e);15 }16 } else {17 try {18// 通过PreparedStatement的API去设置非空参数19setNonNullParameter(ps, i, parameter, jdbcType);20 } catch (Exception e) {21throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType22 + " . "23 + "Try setting a different JdbcType for this parameter or a different configuration property. "24 + "Cause: " + e, e);25 }26 }27 }

1IntegerTypeHandler:2public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)3 throws SQLException {4 ps.setInt(i, parameter);5}

NO.4|总结

Mybatis在执行一次sql请求会经过sqlSession,Executor。在没有命中缓存的情况下,会把查询交给StatementHandler调用statement相关api,与数据库进行交互获取信息。

其中包括创建statement,为statement设置参数,执行查询,映射结果集几个步骤,本篇文章仅提及前两个步骤,后两个步骤会在下一篇文章中讲解~

怎么样, 现在对StatementHandler与ParamHandler是不是有了大概的了解~

这里除了编程知识分享,同时也是我成长脚步的印记, 期待与您一起学习和进步,可以扫描下方二维码关注我的公众号: 程序媛swag。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。