mybatis中的 JDBC处理器 StatementHandler
1、StatementHandler 组件和其他组件之间的调用关系。
MyBatis一个基于JDBC的Dao框架,MyBatis把所有跟JDBC相关的操作全部都放到了StatementHandler中。
一个SQL请求会经过会话,然后是执行器,最由StatementHandler执行jdbc最终到达数据库。其关系如下图:
这里要注意这三者之间比例是1:1:n。也就是说一个sqlsession会对应唯一的一个执行器 和N个StatementHandler。这里的N取决于通过会话调用了多少次Sql,命中缓存除外。
2、StatementHandler 的定义:
JDBC处理器,基于JDBC构建statement,并设置参数,然后执行sql。每次调用会话当中一次sql,都会有与之相对应的且唯一的statement实例。
3、StatementHandler 的结构
StatementHandler是接口,BaseStatementHandler是实现StatementHandler的抽象方法,其中主要放一些SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三个子类的共性操作,比如setStatementTimeout()、setFetchSize等操作。三个子类分别对应JDBC中的Statement、PreparedStatement、CallableStatement。
public interface StatementHandler {/*statement的接口*/ Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;/*声明statement*/ void parameterize(Statement statement) throws SQLException;/*设置参数*/ void batch(Statement statement) throws SQLException;/*添加批处理 并非执行*/ int update(Statement statement) throws SQLException;/*执行更新 对用excutor执行器的update*/ <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;/*执行查询 对用excutor执行器的query*/ <E> Cursor<E> queryCursor(Statement statement) throws SQLException;/*查询游标*/ BoundSql getBoundSql();/**/ ParameterHandler getParameterHandler();/*获取参数处理器*/ }
public abstract class BaseStatementHandler implements StatementHandler { 、、、、、、、 @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { statement = instantiateStatement(connection);/*具体的statement创建交给具体的实现类 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler*/ 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); } } protected abstract Statement instantiateStatement(Connection connection) throws SQLException;/*具体的statement创建交给具体的实现类 */ }
public class PreparedStatementHandler extends BaseStatementHandler { 、、、、、、、 @Override protected Statement instantiateStatement(Connection connection) throws SQLException {/*具体实现,产生一个prepareStatement*/ String sql = boundSql.getSql(); if (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) { return connection.prepareStatement(sql); } else { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } } 、、、、、、 }
4、statementhandler处理流程解析
总共执行过程分为三个阶段:
预处理:这里预处理不仅仅是通过Connection创建Statement,还包括设置参数。
执行:包含执行SQL和处理结果映射两部分。
关闭:直接关闭Statement。
public class SimpleExecutor extends BaseExecutor { 、、、、、、 @Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {/*excutor此处开始进入statementhandler*/ Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);/*通过大管家Configuration 获取statementhandle*/ stmt = prepareStatement(handler, ms.getStatementLog());/*statementhandle来创建statement,设置参数*/ return handler.query(stmt, resultHandler);/*statementhandle来执行查询*/ } finally { closeStatement(stmt); } } 、、、、、、 } public class 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);/*通过包装类RoutingStatementHandler类决定创建哪一种statement处理器 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler .该类存在的意义不大,仅仅做了一个判断,完全可以在Configuration 当前类中完成*/ statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);/*添加插件*/ return statementHandler; } 、、、、、、 } public class RoutingStatementHandler implements StatementHandler { 、、、、、 private final StatementHandler delegate;/包装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()); } 、、、、、、、 } }
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());/*创建statement,首先是basestatementhandler来处理一些共性参数设置,然后是对应的preparestatementhandler等其他处理器来执行具体的创建工作*/
handler.parameterize(stmt);/*设置参数*/
return stmt;
}
其中 statementhandle来执行查询
public class PreparedStatementHandler extends BaseStatementHandler { 、、、、、、 @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute();/*执行statement*/ return resultSetHandler.handleResultSets(ps);/最终使用结果集处理器来处理执行结果/ } 、、、、、、 }
其中statementhandler的类型的设置过程。
UserMapper: @Select({" select * from users where name='${name}'"}) @Options(statementType = StatementType.PREPARED) List<User> selectByName(User user);
具体背后的代码逻辑
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Options { 、、、、、、 boolean useCache() default true; FlushCachePolicy flushCache() default FlushCachePolicy.DEFAULT; ResultSetType resultSetType() default ResultSetType.DEFAULT; StatementType statementType() default StatementType.PREPARED;/*参数*/ int fetchSize() default -1; int timeout() default -1; boolean useGeneratedKeys() default false; String keyProperty() default ""; String keyColumn() default ""; String resultSets() default ""; } public enum StatementType { STATEMENT, PREPARED, CALLABLE /*三种类型*/ }
5、
参数处理
参数处理即将Java Bean转换成数据类型。总共要经历过三个步骤,参数转换、参数映射、参数赋值。
参数转换
即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。
@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@Param("name") String name, @Param("user") User user);
-
单个参数的情况下且没有设置@param注解会直接转换,勿略SQL中的引用名称。
-
多个参数情况:优先采用@Param中设置的名称,如果没有则用参数预号代替 即"param1、parm2...."
-
如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为key
以上所有转换逻辑均在ParamNameResolver中实现。
(1)单个参数:
测试代码: @Select({"select * from users where name=#{name} or age=#{user.age}"}) @Options User selectByNameOrAge(@String name, @Param("user") User user); public void test2() { mapper.selectByNameOrAge("肥仔", Mock.newUser()); }
public class ParamNameResolver { private static final String GENERIC_NAME_PREFIX = "param"; private final SortedMap<Integer, String> names; 、、、、、、 public ParamNameResolver(Configuration config, Method method) { final Class<?>[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<>(); int paramCount = paramAnnotations.length; // get names from @Param annotations for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { if (isSpecialParameter(paramTypes[paramIndex])) { // skip special parameters continue; } String name = null; for (Annotation annotation : paramAnnotations[paramIndex]) { if (annotation instanceof Param) { hasParamAnnotation = true; name = ((Param) annotation).value(); break; } } if (name == null) { // @Param was not specified. if (config.isUseActualParamName()) { name = getActualParamName(method, paramIndex); } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } } map.put(paramIndex, name); } names = Collections.unmodifiableSortedMap(map); } 、、、、、、 public Object getNamedParams(Object[] args) {//args就是CRUD函数入参传入的值[0,肥仔] final int paramCount = names.size();//names就是CRUD函数的入参的名字[1,name],如果没有使用参数注解@param(name),就是[0,arg0;1,user] if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { return args[names.firstKey()];//单个参数不需要和sql语句中的缺省值对应,直接返回 } else { final Map<String, Object> param = new ParamMap<>(); int i = 0; for (Map.Entry<Integer, String> entry : names.entrySet()) { param.put(entry.getValue(), args[entry.getKey()]);//存了两遍 // add generic param names (param1, param2, ...) final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); // ensure not to overwrite parameter named with @Param if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]);// } i++; } return param;//param 就是解析之后的值[arg0,肥仔;parame1,肥仔] //最终基于该map去映射到sql的占位符参数中 } } 、、、、、、 }
arg0基于反射来的,因为parameters 编译参数没有打开,所以直接映射成arg0,arg1。
user是基于注解来的@param
param1,param2是基于顺序来的。
mybatis中获取参数名称的方式是反射。
获取参数的其他方法:
如果是函数的直接通过字节码插装方式可以获得。代码编译后,使用IDEA---->show bytecode 可以看到字节码。如下;可以获取参数名称 。
public class ParamTest { private SqlSession sqlSession; private UserMapper mapper; public static void main(String[] args1,int args2){ } } 编译后的字节码: public static main([Ljava/lang/String;I)V //编译入参的参数名称变了,但是下边的局部变量表中还是有的 L0 LINENUMBER 27 L0 RETURN L1 LOCALVARIABLE args1 [Ljava/lang/String; L0 L1 0 //参数名称1 LOCALVARIABLE args2 I L0 L1 1 //参数名称2 MAXSTACK = 0 MAXLOCALS = 2
参数映射
映射是指Map中的key如何与SQL中绑定的参数相对应。以下这几种情况
-
单个原始类型:直接映射,勿略SQL中引用名称
-
Map类型:基于Map key映射
-
Object:基于属性名称映射,支持嵌套对象属性访问
在Object类型中,支持通过“.”方式映射属中的属性。如:user.age
参数赋值
通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler
测试代码: @Select({"select * from users where name=#{name} or age=#{user.age}"}) @Options User selectByNameOrAge(@String name, @Param("user") User user); public void test2() { mapper.selectByNameOrAge("肥仔", Mock.newUser()); }
public class DefaultParameterHandler implements ParameterHandler { private final Object parameterObject;//参数解析器,解析的map对象 、、、、、、、 public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { this.mappedStatement = mappedStatement; this.configuration = mappedStatement.getConfiguration(); this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); this.parameterObject = parameterObject; //入参,参数解析器,解析的map对象 this.boundSql = boundSql; } @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//获取sql中的占位符参数列表 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();//获取占位符的名称 name ; user.age if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName);//hasAdditionalParameter主要用于多数据库的情况,比如mysql数据库用一个参数,orcal用另一个参数。也就是给参数增加其他的的操作。较少使用
} else if (parameterObject == null) { value = null; }
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//因为只有一个参数时,才能找到对应的类型处理器,map类型没有对应的类型处理器,所以此处能判断是不是单个。
value = parameterObject;//单个参数直接赋值,不管sql中参数名是神马。 } else { MetaObject metaObject = configuration.newMetaObject(parameterObject);//封装参数解析器解析的参数列表
value = metaObject.getValue(propertyName);//通过metaObject工具类获取参数的值,其中对象的嵌套查询(user.age)也是metaobject完成 //使用sql中的参数去 javabean的参数解析列表map中去获取值。
} TypeHandler typeHandler = parameterMapping.getTypeHandler();//获取类型处理器 。xml中没有配置时,默认使用sql中参数类型对应的类型处理器
JdbcType jdbcType = parameterMapping.getJdbcType();//和javabean 也就是CRUD的入参所对应的数据库中的类型
if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); }
try { typeHandler.setParameter(ps, i + 1, value, jdbcType);//类型处理器通过statement来设置参数。最终就是prestatement的设置操作 }
catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } //结束 }
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);//prestatement设置参数
}
5、结果集封装
指读取ResultSet数据,并将每一行转换成相对应的对象。用户可在转换的过程当中可以通过ResultContext来控制是否要继续转换。转换后的对象都会暂存在ResultHandler中最后统一封装成list返回给调用方
resultsethandler一行一行解析处理(行),解析一行将结果发送给resultcontext(一个一个的对象),context主要可以判断是否继续往下解析,可以作为分页使用。最后将结果发送给resulthandle,存放到list中
结果集转换中99%的逻辑DefaultResultSetHandler 中实现。整个流程可大致分为以下阶段:
-
读取结果集
-
遍历结果集当中的行
-
创建对象
-
填充属性