一, 前言
通过前面我们也知道, 通过 getMapper 方式来进行查询, 最后会通过 mapperMehod 类, 对接口中传来的参数也会在这个类里面进行一个解析, 随后就传到对应位置, 与 sql 里面的参数进行一个匹配, 最后获取结果. 对于 mybatis 通常传参 (这里忽略掉 Rowbounds 和 ResultHandler 两种类型) 有几种方式.
1,javabean 类型参数
2, 非 javabean 类型参数
注意, 本文是基于 mybatis3.5.0 版本进行分析.
1, 参数的存储
2, 对 sql 语句中参数的赋值
下面将围绕这这两方面进行
二, 参数的存储
先看下面一段代码
- @Test
- public void testSelectOrdinaryParam() throws Exception{
- SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession();
- UserMapper mapper = sqlSession.getMapper(UserMapper.class);
- List<User> userList = mapper.selectByOrdinaryParam("张三 1 号");
- System.out.println(userList);
- sqlSession.close();
- }
- List<User> selectByOrdinaryParam(String username); // mapper 接口
- <select id="selectByOrdinaryParam" resultMap="BaseResultMap">
- select
- <include refid="Base_Column_List"/>
- from user
- where username = #{username,jdbcType=VARCHAR}
- </select>
或许有的人会奇怪, 这个 mapper 接口没有带 @Param 注解, 怎么能在 mapper 配置文件中直接带上参数名呢, 不是会报错吗,
在 mybatis 里面, 对单个参数而言, 直接使用参数名是没问题的, 如果是多个参数就不能这样了, 下面我们来了解下, mybatis 的解析过程, 请看下面代码, 位于 MapperMehod 类的内部类 MethodSignature 构造函数中
- public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
- Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
- if (resolvedReturnType instanceof Class<?>) {
- this.returnType = (Class<?>) resolvedReturnType;
- } else if (resolvedReturnType instanceof ParameterizedType) {
- this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
- } else {
- this.returnType = method.getReturnType();
- }
- this.returnsVoid = void.class.equals(this.returnType);
- this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
- this.returnsCursor = Cursor.class.equals(this.returnType);
- this.returnsOptional = Optional.class.equals(this.returnType);
- this.mapKey = getMapKey(method);
- this.returnsMap = this.mapKey != null;
- this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
- this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
- // 参数解析类
- this.paramNameResolver = new ParamNameResolver(configuration, method);
- }
参数的存储解析皆由 ParamNameResolver 类来进行操作, 先看下该类的构造函数
- /**
- * config 全局的配置文件中心
- * method 实际执行的方法, 也就是 mapper 接口中的抽象方法
- *
- */
- public ParamNameResolver(Configuration config, Method method) {
- // 获取 method 中的所有参数类型
- final Class<?>[] paramTypes = method.getParameterTypes();
- // 获取参数中含有的注解, 主要是为了 @Param 注解做准备
- 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++) {
- // 排除 RowBounds 和 ResultHandler 两种类型的参数
- if (isSpecialParameter(paramTypes[paramIndex])) {
- // skip special parameters
- continue;
- }
- String name = null;
- // 如果参数中含有 @Param 注解, 则只用 @Param 注解的值作为参数名
- for (Annotation annotation : paramAnnotations[paramIndex]) {
- if (annotation instanceof Param) {
- hasParamAnnotation = true;
- name = ((Param) annotation).value();
- break;
- }
- }
- // 即参数没有 @Param 注解
- if (name == null) {
- // 参数实际名称, 其实这个值默认就是 true, 具体可以查看 Configuration 类中的该属性值, 当然也可以在配置文件进行配置关闭
- // 如果 jdk 处于 1.8 版本, 且编译时带上了 - parameters 参数, 那么获取的就是实际的参数名, 如 methodA(String username)
- // 获取的就是 username, 否则获取的就是 args0 后面的数字就是参数所在位置
- if (config.isUseActualParamName()) {
- name = getActualParamName(method, paramIndex);
- }
- // 如果以上条件都不满足, 则将参数名配置为 0,1,2../
- 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);
- }
这个构造函数的作用就是对参数名称进行一个封装, 得到一个 "参数位置 -->参数名称" 的一个 map 结构, 这样做的目的是为了替换参数值, 我们也清楚, 实际传过来的参数就是一个一个 Object 数组结构, 我们也可以将它理解为 map 结构. 即 index --> 参数值, 此就和之前的 map 结构有了对应, 也就最终可以得到一个 参数名称 ---> 参数值 的一个对应关系.
- public Object execute(SqlSession sqlSession, Object[] args) {
- Object result;
- switch (command.getType()) {
- // 其它情况忽略掉
- case SELECT:
- // 这里参数中含有 resultHandler, 暂不做讨论
- if (method.returnsVoid() && method.hasResultHandler()) {
- executeWithResultHandler(sqlSession, args);
- result = null;
- } else if (method.returnsMany()) {// 1, 返回结果为集合类型或数组类型, 这种情况适用于大多数情况
- result = executeForMany(sqlSession, args);
- } else if (method.returnsMap()) {// 返回结果为 Map 类型
- result = executeForMap(sqlSession, args);
- } else if (method.returnsCursor()) {
- result = executeForCursor(sqlSession, args);
- } else {// 2, 返回结果 javabean 类型, 或普通的基础类型及其包装类等
- Object param = method.convertArgsToSqlCommandParam(args);
- result = sqlSession.selectOne(command.getName(), param);
- // 对 java8 中的 optional 进行了支持
- if (method.returnsOptional() &&
- (result == null || !method.getReturnType().equals(result.getClass()))) {
- result = Optional.ofNullable(result);
- }
- }
- break;
- default:
- 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;
- }
这里主要分析 1 情况. 对于 2 情况也就是接下来要说的参数赋值情况, 不过要先介绍下 method.convertArgsToSqlCommandParam 这代码带来的一个结果是怎么样的
- public Object convertArgsToSqlCommandParam(Object[] args) {
- return paramNameResolver.getNamedParams(args);
- }
- public Object getNamedParams(Object[] args) {
- final int paramCount = names.size();
- if (args == null || paramCount == 0) {
- return null;
- } else if (!hasParamAnnotation && paramCount == 1) {// 1
- return args[names.firstKey()];
- } 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;
- }
- }
可以很清楚的知道最后又调用了 ParamNameResolver 类的 getNamedPaams 方法, 这个方法的主要作用就是, 将原来的参数位置 --> 参数名称 映射关系转为 参数名称 --->参数值 , 并且新加一个参数名和参数值得一个对应关系. 即
param1 ->参数值 1
param2 -->参数值 2
当然如果只有一个参数, 如代码中的 1 部分, 若参数没有 @Param 注解, 且只有一个参数, 则不会加入上述的一个对象关系, 这也就是前面说的, 对于单个参数, 可以直接在 sql 中写参数名就 ok 的原因. 下面回到前面
- private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
- List<E> result;
- // 获取对应的一个映射关系, param 类型有可能为 map 或 null 或参数实际类型
- Object param = method.convertArgsToSqlCommandParam(args);
- if (method.hasRowBounds()) {
- RowBounds rowBounds = method.extractRowBounds(args);
- result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
- } else {
- result = sqlSession.<E>selectList(command.getName(), param);
- }
- // 如果返回结果类型和 method 的返回结果类型不一致, 则进行转换数据结构
- // 其实就是 result 返回结果不是 List 类型, 而是其他集合类型或数组类型
- if (!method.getReturnType().isAssignableFrom(result.getClass())) {
- if (method.getReturnType().isArray()) {// 为数组结果
- return convertToArray(result);
- } else {// 其他集合类型
- return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
- }
- }
- return result;
- }
代码也不复杂, 就是将得到的参数对应关系传入, 最终获取结果, 根据实际需求进行结果转换.
3, 对 sql 语句中参数的赋值
其实前面一篇博客中也有涉及到. 参数赋值的位置在 DefaultParameterHandler 类里面, 可以查看前面一篇博客, 这里不做过多介绍, 传送门 mybatis 查询语句的背后之封装数据
--------------------------------------------------------------------------------------------------------------------------------------- 分界线 --------------------------------------------------------------------------------------------------------
若有不足或错误之处, 还望指正, 谢谢!
来源: https://www.cnblogs.com/qm-article/p/10658527.html