资讯专栏INFORMATION COLUMN

mybatis参数解析

shevy / 1214人阅读

摘要:一简介是支持定制化存储过程以及高级映射的优秀的持久层框架。避免了几乎所有的代码和手动设置参数以及获取结果集。可以对配置和原生使用简单的或注解,将接口和的普通的对象映射成数据库中的记录。三其他类型的参数我懒,不想写了我的心愿是世界和平

(一)MyBatis简介

MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

(二)源码分析

项目是用映射器(dao)和映射文件(xxx.xml)的方式配置mybatis

先以下面的更新方法为例

dao接口的方法如下:
int updateSubjectById(Subject subject)throws Exception;

代码执行dao方法时,会调用MapperProxy类中的invoke()方法,往下执行会依次会调用
MapperMethod中的public Object execute(SqlSession sqlSession, Object[] args),

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
        if (method.returnsVoid() && method.hasResultHandler()) {
            executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (method.returnsMany()) {
            result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
            result = executeForMap(sqlSession, args);
        } else {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
        }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method "" + command.getName() 
            + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

MethodSignature中的public Object convertArgsToSqlCommandParam(Object[] args),

public Object convertArgsToSqlCommandParam(Object[] args) {
    final int paramCount = params.size();
    if (args == null || paramCount == 0) {
        return null;
    } else if (!hasNamedParameters && paramCount == 1) {
        return args[params.keySet().iterator().next().intValue()];
    } else {
        final Map param = new ParamMap();
        int i = 0;
        for (Map.Entry entry : params.entrySet()) {
            param.put(entry.getValue(), args[entry.getKey().intValue()]);
            // issue #71, add param names as param1, param2...but ensure backward compatibility
            final String genericParamName = "param" + String.valueOf(i + 1);
            if (!param.containsKey(genericParamName)) {
                param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
        }
        return param;
    }
}

因为dao中方法的参数为Subject subject,所以这里args参数为

hasNamedParameters意思是是否使用了注解参数,这里没有用到注解,值为false。
这里params.size()的值为1,如图

所以上面的方法返回的是subject,excute()中的param就是subject。
convertArgsToSqlCommandParam()方法执行完后,依次进入rowCountResult(sqlSession.update(command.getName(), param)),SqlSessionTemplate中update()方法,

public int update(String statement, Object parameter) {
    return this.sqlSessionProxy.update(statement, parameter);
}

这时SqlSessionInterceptor拦截器(SqlSessiontemplate中的私有类)会拦截到。

private class SqlSessionInterceptor implements InvocationHandler {
    ...
    Object result=method.invoke(sqlSession, args);
    ...
}

其中arg[0]的值为com.services.forum.dao.SubjectDAO.updateSubjectById()方法,args[1]的值为SqlSessionTemplate中public int update(String statement, Object parameter)中的parameter,即dao层方法中传的subject对象。
this.sqlSessionProxy的类型为DefaultSqlSession,其中update(...)方法return executor.update(ms, wrapCollection(parameter)); ,wrapCollection(parameter)对参数进行包装,该方法的实现如下:

private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
        StrictMap map = new StrictMap();
        map.put("collection", object);
        if (object instanceof List) {
            map.put("list", object);
        }
        return map;
    } else if (object != null && object.getClass().isArray()) {
        StrictMap map = new StrictMap();
        map.put("array", object);
        return map;
    }
    return object;
}

此方法首先定义一个StrictMap,

如果参数类型是Collection的子类,则在map中加入一条entry:("collection", object),返回map

如果参数类型是List的子类,则在map中加入一条entry:("list", object),返回map

如果参数是数组,则在map中加入一条entry:("array", object),返回map

否则返回参数object

随后会执行以下方法:
SimpleExecutor
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Configuration
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandl    er resultHandler, BoundSql boundSql){
    ...
    delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    ...
} 
PreparedStatementHandler
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBound    s, ResultHandler resultHandler, BoundSql boundSql) {
      super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
BaseStatementHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds row    Bounds, ResultHandler resultHandler, BoundSql boundSql) {
    ...
    boundSql = mappedStatement.getBoundSql(parameterObject);
    ...
}
MappedStatement
public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    ...
}

再跟几步会进入DynamicSqlSource类中的getBoundSql()方法

public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry entry : context.getBindings().entrySet()) {
        boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
  return boundSql;
}

这个方法是生成sql语句的方法
sqlSourceParser中是设置和一些注册的类型[处理机的]别名,如下图

再跟到SqlSourceBuilder中的parse()方法,

public SqlSource parse(String originalSql, Class parameterType, Map additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalPara        meters);
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

该方法中有个 originalSql参数,这是sql映射文件中的原始sql语句,

update t_forum_subject SET title=#{title}, brief=#{brief}, is_top=#{isTop}, is_open=#{isOpen}, last_modify_time=#{lastModifyTime}, last_modify_userid=#{lastModifyUserId} where id=#{id} and enable=1 

GenericTockenParser类中的public String parse(String text)方法将originalSql中的#{}转为了?,并且在handler的属性parameterMappings中添加相应的映射

public String parse(String text) {
    ...
    builder.append(handler.handleToken(content));
    ...
}
SqlSourceBuilder
@Override
public String handleToken(String content) {
    parameterMappings.add(buildParameterMapping(content));
    return "?";
}


再回到DynamicSqlSource类中getBoundSql方法中,
现在sqlSource中sql属性值变为

update t_forum_subject SET title=?, brief=?, is_top=?, is_open=?, last_modify_time=?, last_modify_userid=? 
where id=? and enable=1


parmeterMappings属性的值如上图所示

boundSql的值如上图

SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
}

最后进入DefaultParameterhandler类中的

public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  List parameterMappings = boundSql.getParameterMappings();
  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();
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        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);
        }
      }
    }
  }
}

在这个方法中遍历parameterMappings,将parameterMapping中property的值赋给propertyName,再通过

MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);

将subject对象中的值赋给value,然后typeHandler.setParameter(ps, i + 1, value, jdbcType);将value值加入到ps的ColumnMap, ColumnNames, ColumnValues,再执行SimpleExecutor的doUpdate()中的return handler.update(stmt);

然后,就没有然后了

这次更新操作就完成了。
(三)其他类型的参数

我懒,不想写了

---------------------------------------EOF--------------------------------------

我的心愿是世界和平!~( ゜▽゜)つロ

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69820.html

相关文章

  • Mybatis源码分析

    摘要:我认为学习框架源码分为两步抓住主线,掌握框架的原理和流程理解了处理思路之后,再去理解面向对象思想和设计模式的用法目前第一步尚有问题,需要多走几遍源码,加深下理解,一起加油 这篇文章我们来深入阅读下Mybatis的源码,希望以后可以对底层框架不那么畏惧,学习框架设计中好的思想; 架构原理 架构图 showImg(https://segmentfault.com/img/remote/...

    lindroid 评论0 收藏0
  • mybatis深入理解(一)之 # 与 $ 区别以及 sql 预编译

    摘要:在动态解析阶段,和会有不同的表现解析为一个预编译语句的参数标记符。其次,在预编译之前已经被变量替换了,这会存在注入问题。预编译语句对象可以重复利用。默认情况下,将对所有的进行预编译。总结本文主要深入探究了对和的不同处理方式,并了解了预编译。 mybatis 中使用 sqlMap 进行 sql 查询时,经常需要动态传递参数,例如我们需要根据用户的姓名来筛选用户时,sql 如下: sele...

    shadowbook 评论0 收藏0
  • MyBatis 源码分析系列文章合集

    摘要:简介我从七月份开始阅读源码,并在随后的天内陆续更新了篇文章。考虑到超长文章对读者不太友好,以及拆分文章工作量也不小等问题。经过两周紧张的排版,一本小小的源码分析书诞生了。我在写系列文章中,买了一本书作为参考,这本书是技术内幕。 1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章。起初,我只是打算通过博客的形式进行分享。但在写作的过程中,发现要分析的代码...

    Crazy_Coder 评论0 收藏0
  • mybatis-spring原理解析

    摘要:创建出的是对象,持有这个对象。根据接口名和方法名从对象的中检查并获取方法对应的语句解析成的对象,保存它的和命令类型。实现类拦截映射接口的自定义方法,让去处理方法对应的解析成的。 前言 Mybatis是目前主流的Java ORM框架之一。mybatis-spring包则是为了让Mybatis更好得整合进Spring的衍生产品。本文就从Mybatis和mybatis-spring源码着手,...

    why_rookie 评论0 收藏0
  • 【深入浅出MyBatis笔记】MyBatis解析和运行原理

    摘要:的解析和运行原理构建过程提供创建的核心接口。在构造器初始化时会根据和的方法解析为命令。数据库会话器定义了一个对象的适配器,它是一个接口对象,构造器根据配置来适配对应的对象。它的作用是给实现类对象的使用提供一个统一简易的使用适配器。 MyBatis的解析和运行原理 构建SqlSessionFactory过程 SqlSessionFactory提供创建MyBatis的核心接口SqlSess...

    bitkylin 评论0 收藏0
  • Mybatis系列】从源码角度理解Mybatis的$和#的作用

    摘要:原因就是传入的和原有的单引号,正好组成了,而后面恒等于,所以等于对这个库执行了查所有的操作。类比的执行流程和原有的我们使用的方法就是。可以理解为就是用来解析定制的符号的语句。后续的流程,就和正常的流程一致了。 前言 在JDBC中,主要使用的是两种语句,一种是支持参数化和预编译的PrepareStatement,能够支持原生的Sql,也支持设置占位符的方式,参数化输入的参数,防止Sql注...

    yanwei 评论0 收藏0

发表评论

0条评论

shevy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<