JDBC的知识对于JAVA开发人员来讲在简单不过的知识了。PreparedStatement的作用更是胸有成竹。我们最常见用到有俩个方法:executeQuery方法和executeUpdate方法。这俩个方法之外还有一个execute方法。只是这个方法我们很少用。但是mybatis框架就是却用这个方法来实现的。不管mybatis用是哪一个方法来实现。有一点可以肯定——那就是必须得到Statement接口实例。你可以这样子理解mybatis把如何获得Statement接口实例做了一个完美的封装。而这一个封装就是上一章出现的StatementHandler接口。
mybatis里面实现StatementHandler接口有四个类。
RoutingStatementHandler类:笔者把它理解为下面三个类的代理类。
CallableStatementHandler类:对应处理JDBC里面的CallableStatement类。
PreparedStatementHandler类:对应处理JDBC里面的PreparedStatement类。
SimpleStatementHandler类:对应处理JDBC里面的一般Statement接口实例(笔者也不知道JDBC是需叫他什么)。
正如上面所讲的笔者把RoutingStatementHandler类理解为三个类的代理类。mybatis并没有直接去引用后面三个类。而是通过RoutingStatementHandler类来判断当前到底要调用哪个类。再去执行相关的Statement接口实例。
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; }
这一段源码就是前一章尾部源码的后继执行。源码的意图就是新建一个RoutingStatementHandler类实例。关键的点是在RoutingStatementHandle类的构造函数里面。
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()); } }
从这里就可以看出笔者为什么说RoutingStatementHandler类可以理解为三个类的代理类。事实上所有的工作都是内部成员delegate来做的。而delegate又是在构造函数里面进行判断生成的。看样子在这里JDBC的三种操作方式完美的体现出来。通过MappedStatement的getStatementType方法得到相应返回值,判断当前SQL语句是要用哪一种操作方式来进行。默认情况下是用Prepared方式。当前笔者不是瞎说的。在MappedStatement的Builder方法里就已经设置了。请读者们自行查看。
1 public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) { 2 mappedStatement.configuration = configuration; 3 mappedStatement.id = id; 4 mappedStatement.sqlSource = sqlSource; 5 mappedStatement.statementType = StatementType.PREPARED; 6 mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build(); 7 mappedStatement.resultMaps = new ArrayList<ResultMap>(); 8 mappedStatement.sqlCommandType = sqlCommandType; 9 mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); 10 String logId = id; 11 if (configuration.getLogPrefix() != null) { 12 logId = configuration.getLogPrefix() + id; 13 } 14 mappedStatement.statementLog = LogFactory.getLog(logId); 15 mappedStatement.lang = configuration.getDefaultScriptingLanuageInstance(); 16 }
如果实在不想用默认的方式进行处理的话,可以在相关每一个XML节点的statementType属性进行设置。如下
<select id="SelectProducts" resultMap="result" statementType="STATEMENT" > select * from Products where #{0} > ProductID and ProductName like #{1} </select>
生成Statement接口实例要用到StatementHandler接口的俩个方法:prepare方法和parameterize方法。prepare方法用于完成构建Statement接口实例。parameterize方法用于处理Statement接口实例对应的参数。理解这一过程需要调头查看SimpleExecutor类的doQuery方法。
1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 2 Statement stmt = null; 3 try { 4 Configuration configuration = ms.getConfiguration(); 5 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 6 stmt = prepareStatement(handler, ms.getStatementLog()); 7 return handler.<E>query(stmt, resultHandler) ; 8 } finally { 9 closeStatement(stmt); 10 } 11 }
源码的prepareStatement方法里面可以体现prepare方法和parameterize方法的作用。通过prepareStatement方法就可以得到一个完整Statement接口实例。最后在通过StatementHandler接口实例的query方法来获得对应的结果。笔者暂且跳过这一个过程(query方法处理结果)。让我们来看看关于prepare方法和parameterize方法。
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; }
上面说到prepare方法就是用于构建Statement接口实例。默认情况是PreparedStatementHandler类。那么笔者就拿PreparedStatementHandler类来切入吧。当笔者点开PreparedStatementHandler类的源码,试着去查看一下prepare方法。发现找不到。原来他在PreparedStatementHandler类的父类(BaseStatementHandler类)里面。
1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { 2 ErrorContext.instance().sql(boundSql.getSql()); 3 Statement statement = null; 4 try { 5 statement = instantiateStatement(connection); 6 setStatementTimeout(statement, transactionTimeout); 7 setFetchSize(statement); 8 return statement; 9 } catch (SQLException e) { 10 closeStatement(statement); 11 throw e; 12 } catch (Exception e) { 13 closeStatement(statement); 14 throw new ExecutorException("Error preparing statement. Cause: " + e, e); 15 } 16 }
每一个框架都有一个共同的特点——方法调来调去的。prepare方法里面通过instantiateStatement方法来返回相关的Statement实例。而这个方法却是一个抽象方法。
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
他的实例就是在各自的子类里面。完美的利用了继承的好处。
1 protected Statement instantiateStatement(Connection connection) throws SQLException { 2 String sql = boundSql.getSql(); 3 if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { 4 String[] keyColumnNames = mappedStatement.getKeyColumns(); 5 if (keyColumnNames == null) { 6 return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); 7 } else { 8 return connection.prepareStatement(sql, keyColumnNames); 9 } 10 } else if (mappedStatement.getResultSetType() != null) { 11 return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); 12 } else { 13 return connection.prepareStatement(sql); 14 } 15 }
上面的源码是PreparedStatementHandler类的。所以不用笔者多讲——就是生成PreparedStatement实例。
有了PreparedStatement实例,当然就要对他进行设置相应的参数。这也是parameterize方法的作用。但是如何是简单的设置那显然没有什么可说的。主要还是因为mybatis对于设置参数方面做精心的设计。好话不多说。还是看一下源码最实在。
public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }
ParameterHandler接口的作用显然不用笔者多讲。DefaultParameterHandler类便是他的实例类。DefaultParameterHandler类的代码不多,可是他包含的内容却很多。进去看一下就知道了。
1 public void setParameters(PreparedStatement ps) { 2 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); 3 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 4 if (parameterMappings != null) { 5 for (int i = 0; i < parameterMappings.size(); i++) { 6 ParameterMapping parameterMapping = parameterMappings.get(i); 7 if (parameterMapping.getMode() != ParameterMode.OUT) { 8 Object value; 9 String propertyName = parameterMapping.getProperty(); 10 if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params 11 value = boundSql.getAdditionalParameter(propertyName); 12 } else if (parameterObject == null) { 13 value = null; 14 } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { 15 value = parameterObject; 16 } else { 17 MetaObject metaObject = configuration.newMetaObject(parameterObject); 18 value = metaObject.getValue(propertyName); 19 } 20 TypeHandler typeHandler = parameterMapping.getTypeHandler(); 21 JdbcType jdbcType = parameterMapping.getJdbcType(); 22 if (value == null && jdbcType == null) { 23 jdbcType = configuration.getJdbcTypeForNull(); 24 } 25 try { 26 typeHandler.setParameter(ps, i + 1, value, jdbcType); 27 } catch (TypeException e) { 28 throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); 29 } catch (SQLException e) { 30 throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); 31 } 32 } 33 } 34 }
BoundSql类又一次出现在我们的面前,前面笔者也没有提过关于BoundSql类的作用。因为如果没有一个上下文的作用是很难推断出BoundSql类。笔者也只是从源码来看的,也不一定是对的。前面部分的源码里面有出现过使用MappedStatement类。他可以说是一个包含节点(select节点,update节点等)信息的类。但是对的具体的SQL语句用到的信息却很少。那么BoundSql类就是存放于的组装SQL句语信息。从源码里面我们可以看到BoundSql类处理返回结果的信息却没有。有的只是SQL语句的参数之类的信息。如下他的内部成员。
private String sql; private List<ParameterMapping> parameterMappings; private Object parameterObject; private Map<String, Object> additionalParameters; private MetaObject metaParameters;
有了对BoundSql类的概念认识,我们接着谈谈上面源码(setParameters方法部分)里面发生的事情吧。如果想要一下就明白他是做什么的怎么样子做。那笔者只能说自己功力不行。笔者只能大概的看出他在做什么。通过BoundSql类获得相应的ParameterMapping类。找到对应的属性名(如:#{id})。接着通过传入的参数信息获得对应的MetaObject类。在通过MetaObject类和属性名获得相应属性名的值。最后一步就是通过TypeHandler接口实例设置值了。
到了这里面StatementHandler接口的工作算是结束了。对于MetaObject类是如何获得的,他又是什么。笔者这里就不多加言论。笔者留意的点还是TypeHandler接口。这部分的知识点官网也讲到过——typeHanlders。了解TypeHandler接口的源码也就是成了下一个目标了。