一文带你读懂MyBatis中的参数?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
创新互联专注为客户提供全方位的互联网综合服务,包含不限于成都网站建设、做网站、丽水网络推广、微信小程序开发、丽水网络营销、丽水企业策划、丽水品牌公关、搜索引擎seo、人物专访、企业宣传片、企业代运营等,从售前售中售后,我们都将竭诚为您服务,您的肯定,是我们最大的嘉奖;创新互联为所有大学生创业者提供丽水建站搭建服务,24小时服务热线:13518219792,官方网址:www.cdcxhl.com
前言
相信很多人可能都遇到过下面这些异常:
"Parameter 'xxx' not found. Available parameters are [...]"
"Could not get property 'xxx' from xxxClass. Cause:
"The expression 'xxx' evaluated to a null value."
"Error evaluating expression 'xxx'. Return value (xxxxx) was not iterable."
不只是上面提到的这几个,我认为有很多的错误都产生在和参数有关的地方。
想要避免参数引起的错误,我们需要深入了解参数。
想了解参数,我们首先看MyBatis处理参数和使用参数的全部过程。
本篇由于为了便于理解和深入,使用了大量的源码,因此篇幅较长,需要一定的耐心看完,本文一定会对你起到很大的帮助。
参数处理过程
处理接口形式的入参
在使用MyBatis时,有两种使用方法。一种是使用的接口形式,另一种是通过SqlSession调用命名空间。这两种方式在传递参数时是不一样的,命名空间的方式更直接,但是多个参数时需要我们自己创建Map作为入参。相比而言,使用接口形式更简单。
接口形式的参数是由MyBatis自己处理的。如果使用接口调用,入参需要经过额外的步骤处理入参,之后就和命名空间方式一样了。
在MapperMethod.Java会首先经过下面方法来转换参数:
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()]; } else { final Map param = new ParamMap(); int i = 0; for (Map.Entry entry : params.entrySet()) { param.put(entry.getValue(), args[entry.getKey()]); // 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; } }在这里有个很关键的params,这个参数类型为Map ,他会根据接口方法按顺序记录下接口参数的定义的名字,如果使用@Param指定了名字,就会记录这个名字,如果没有记录,那么就会使用它的序号作为名字。例如有如下接口:List select(@Param('sex')String sex,Integer age);那么他对应的params如下:{ 0:'sex', 1:'1' }继续看上面的convertArgsToSqlCommandParam方法,这里简要说明3种情况:入参为null或没有时,参数转换为null没有使用@Param注解并且只有一个参数时,返回这一个参数使用了@Param注解或有多个参数时,将参数转换为Map1类型,并且还根据参数顺序存储了key为param1,param2的参数。 注意:从第3种情况来看,建议各位有多个入参的时候通过@Param指定参数名,方便后面(动态sql)的使用。经过上面方法的处理后,在MapperMethod中会继续往下调用命名空间方式的方法:Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectList(command.getName(), param);从这之后开始按照统一的方式继续处理入参。处理集合不管是selectOne还是selectMap方法,归根结底都是通过selectList进行查询的,不管是delete还是insert方法,都是通过update方法操作的。在selectList和update中所有参数的都进行了统一的处理。在DefaultSqlSession.java中的wrapCollection方法: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; }这里特别需要注意的一个地方是map.put("collection", object) ,这个设计是为了支持Set类型,需要等到MyBatis 3.3.0版本才能使用。wrapCollection处理的是只有一个参数时,集合和数组的类型转换成Map2类型,并且有默认的Key,从这里你能大概看到为什么中默认情况下写的array和list(Map类型没有默认值map)。参数的使用参数的使用分为两部分:第一种就是常见#{username}或者${username} 。第二种就是在动态SQL中作为条件,例如 。 下面对这两种进行详细讲解,为了方便理解,先讲解第二种情况。在动态SQL条件中使用参数关于动态SQL的基础内容可以查看官方文档。动态SQL为什么会处理参数呢?主要是因为动态SQL中的 , , 都会用到表达式,表达式中会用到属性名,属性名对应的属性值如何获取呢?获取方式就在这关键的一步。不知道多少人遇到Could not get property xxx from xxxClass或: Parameter ‘xxx' not found. Available parameters are[…] ,都是不懂这里引起的。在DynamicContext.java中,从构造方法看起:public DynamicContext(Configuration configuration, Object parameterObject) { if (parameterObject != null && !(parameterObject instanceof Map)) { MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject); } else { bindings = new ContextMap(null); } bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId()); }这里的Object parameterObject就是我们经过前面两步处理后的参数。这个参数经过前面两步处理后,到这里的时候,他只有下面三种情况:null,如果没有入参或者入参是null,到这里也是null。Map类型,除了null之外,前面两步主要是封装成Map类型。数组、集合和Map以外的Object类型,可以是基本类型或者实体类。看上面构造方法,如果参数是1,2情况时,执行代码bindings = new ContextMap(null);参数是3情况时执行if中的代码。我们看看ContextMap类,这是一个内部静态类,代码如下:static class ContextMap extends HashMap { private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) { this.parameterMetaObject = parameterMetaObject; } public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { // issue #61 do not modify the context when reading return parameterMetaObject.getValue(strKey); } return null; } }我们先继续看DynamicContext的构造方法,在if/else之后还有两行:bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());其中两个Key分别为:public static final String PARAMETER_OBJECT_KEY = "_parameter"; public static final String DATABASE_ID_KEY = "_databaseId";也就是说1,2两种情况的时候,参数值只存在于"_parameter"的键值中。3情况的时候,参数值存在于"_parameter"的键值中,也存在于bindings本身。当动态SQL取值的时候会通过OGNL从bindings中获取值。MyBatis在OGNL中注册了ContextMap:static { OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor()); }当从ContextMap取值的时候,会执行ContextAccessor中的如下方法:@Override public Object getProperty(Map context, Object target, Object name) throws OgnlException { Map map = (Map) target; Object result = map.get(name); if (map.containsKey(name) || result != null) { return result; } Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } return null; }参数中的target就是ContextMap类型的,所以可以直接强转为Map类型。参数中的name就是我们写在动态SQL中的属性名。下面举例说明这三种情况:1、null的时候:不管name是什么(name="_databaseId"除外,可能会有值),此时Object result = map.get(name);得到的result=null。在Object parameterObject = map.get(PARAMETER_OBJECT_KEY);中parameterObject=null,因此最后返回的结果是null。在这种情况下,不管写什么样的属性,值都会是null,并且不管属性是否存在,都不会出错。2、Map类型:此时Object result = map.get(name);一般也不会有值,因为参数值只存在于"_parameter"的键值中。然后到Object parameterObject = map.get(PARAMETER_OBJECT_KEY); ,此时获取到我们的参数值。在从参数值((Map)parameterObject).get(name)根据name来获取属性值。在这一步的时候,如果name属性不存在,就会报错:throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());name属性是什么呢,有什么可选值呢?这就是处理接口形式的入参和处理集合处理后所拥有的Key。如果你遇到过类似异常,相信看到这儿就明白原因了。3、数组、集合和Map以外的Object类型:这种类型经过了下面的处理:MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject);MetaObject是MyBatis的一个反射类,可以很方便的通过getValue方法获取对象的各种属性(支持集合数组和Map,可以多级属性点.访问,如user.username,user.roles[1].rolename) 。现在分析这种情况。首先通过name获取属性时Object result = map.get(name); ,根据上面ContextMap类中的get方法:public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { return parameterMetaObject.getValue(strKey); } return null; }可以看到这里会优先从Map中取该属性的值,如果不存在,那么一定会执行到下面这行代码:return parameterMetaObject.getValue(strKey)如果name刚好是对象的一个属性值,那么通过MetaObject反射可以获取该属性值。如果该对象不包含name属性的值,就会报错:throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + t.toString(), t);理解这三种情况后,使用动态SQL应该不会有参数名方面的问题了。在SQL语句中使用参数SQL中的两种形式#{username}或者${username} ,虽然看着差不多,但是实际处理过程差别很大,而且很容易出现莫名其妙的错误。${username}的使用方式为OGNL方式获取值,和上面的动态SQL一样,这里先说这种情况。${propertyName}参数在TextSqlNode.java中有一个内部的静态类BindingTokenParser,现在只看其中的handleToken方法:@Override public String handleToken(String content) { Object parameter = context.getBindings().get("_parameter"); if (parameter == null) { context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { context.getBindings().put("value", parameter); } Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" checkInjection(srtValue); return srtValue; }从put("value"这个地方可以看出来,MyBatis会创建一个默认为"value"的值,也就是说,在xml中的SQL中可以直接使用${value},从else if可以看出来,只有是简单类型的时候,才会有值。关于这点,举个简单例子,如果接口为List selectOrderby(String column) ,如果xml内容为: select * from user order by ${value} 这种情况下,虽然没有指定一个value属性,但是MyBatis会自动把参数column赋值进去。再往下的代码:Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value));这里和动态SQL就一样了,通过OGNL方式来获取值。看到这里使用OGNL这种方式时,你有没有别的想法?特殊用法:你是否在SQL查询中使用过某些固定的码值?一旦码值改变的时候需要改动很多地方,但是你又不想把码值作为参数传进来,怎么解决呢?你可能已经明白了。就是通过OGNL的方式,例如有如下一个码值类:package com.abel533.mybatis; public interface Code{ public static final String ENABLE = "1"; public static final String DISABLE = "0"; }如果在xml,可以这么使用: select * from user where enable = ${@com.abel533.mybatis.Code@ENABLE} 除了码值之外,你可以使用OGNL支持的各种方法,如调用静态方法。#{propertyName}参数这种方式比较简单,复杂属性的时候使用的MyBatis的MetaObject。在DefaultParameterHandler.java中:public void setParameters(PreparedStatement ps) throws SQLException { 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(); } typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } }上面这段代码就是从参数中取#{propertyName}值的方法,这段代码的主要逻辑就是if/else判断的地方,单独拿出来分析: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); }首先看第一个if,当使用的时候,MyBatis会自动生成额外的动态参数,如果propertyName是动态参数,就会从动态参数中取值。第二个if,如果参数是null,不管属性名是什么,都会返回null。第三个if,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。最后一个else,这种情况下是复杂对象或者Map类型,通过反射方便的取值。下面我们说明上面四种情况下的参数名注意事项。1、动态参数,这里的参数名和值都由MyBatis动态生成的,因此我们没法直接接触,也不需要管这儿的命名。但是我们可以了解一下这儿的命名规则,当以后错误信息看到的时候,我们可以确定出错的地方。在ForEachSqlNode.java中:private static String itemizeItem(String item, int i) { return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString(); }其中ITEM_PRFIX为public static final String ITEM_PREFIX = "__frch_"; 。如果在中的collection="userList" item="user" , 那么对userList循环产生的动态参数名就是:__frch_user_0,__frch_user_1,__frch_user_2…如果访问动态参数的属性,如user.username会被处理成__frch_user_0.username ,这种参数值的处理过程在更早之前解析SQL的时候就已经获取了对应的参数值。具体内容看下面有关的详细内容。2、参数为null,由于这里的判断和参数名无关,因此入参null的时候,在xml中写的#{name}不管name写什么,都不会出错,值都是null。3、可以直接使用typeHandler处理的类型。最常见的就是基本类型,例如有这样一个接口方法User selectById(@Param("id")Integer id) ,在xml中使用id的时候,我们可以随便使用属性名,不管用什么样的属性名,值都是id。4、复杂对象或者Map类型一般都是我们需要注意的地方,这种情况下,就必须保证入参包含这些属性,如果没有就会报错。这一点和可以参考上面有关MetaObject的地方。详解所有动态SQL类型中, 似乎是遇到问题最多的一个。例如有下面的方法: INSERT INTO user(username,password) VALUES (#{user.username},#{user.password}) 对应的接口:int insertUserList(@Param("userList")List list);我们通过foreach源码,看看MyBatis如何处理上面这个例子。在ForEachSqlNode.java中的apply方法中的前两行:Map bindings = context.getBindings(); final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);这里的bindings参数熟悉吗?上面提到过很多。经过一系列的参数处理后,这儿的bindings如下:{ "_parameter":{ "param1":list, "userList":list }, "_databaseId":null, }collectionExpression就是collection="userList"的值userList。我们看看evaluator.evaluateIterable如何处理这个参数,在ExpressionEvaluator.java中的evaluateIterable方法:public Iterable<?> evaluateIterable(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value == null) { throw new BuilderException("The expression '" + expression + "' evaluated to a null value."); } if (value instanceof Iterable) { return (Iterable<?>) value; } if (value.getClass().isArray()) { int size = Array.getLength(value); List answer = new ArrayList(); for (int i = 0; i < size; i++) { Object o = Array.get(value, i); answer.add(o); } return answer; } if (value instanceof Map) { return ((Map) value).entrySet(); } throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable."); }首先通过看第一行代码:Object value = OgnlCache.getValue(expression, parameterObject);这里通过OGNL获取到了userList的值。获取userList值的时候可能出现异常,具体可以参考上面动态SQL部分的内容。userList的值分四种情况。value == null,这种情况直接抛出异常BuilderException。value instanceof Iterable,实现Iterable接口的直接返回,如Collection的所有子类,通常是List。value.getClass().isArray()数组的情况,这种情况会转换为List返回。value instanceof Map如果是Map,通过((Map) value).entrySet()返回一个Set类型的参数。通过上面处理后,返回的值,是一个Iterable类型的值,这个值可以使用for (Object o : iterable)这种形式循环。在ForEachSqlNode中对iterable循环的时候,有一段需要关注的代码:if (o instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapEntry = (Map.Entry) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { applyIndex(context, i, uniqueNumber); applyItem(context, o, uniqueNumber); }如果是通过((Map) value).entrySet()返回的Set,那么循环取得的子元素都是Map.Entry类型,这个时候会将mapEntry.getKey()存储到index中,mapEntry.getValue()存储到item中。如果是List,那么会将序号i存到index中,mapEntry.getValue()存储到item中。常见错误补充当collection="userList"的值userList中的User是一个继承自Map的类型时,你需要保证循环中用到的所有对象的属性必须存在,Map类型存在的问题通常是,如果某个值是null,一般是不存在相应的key,这种情况会导致出错,会报找不到__frch_user_x参数。所以这种情况下,就是值是null,你也需要map.put(key,null) 。看完上述内容,你们掌握一文带你读懂MyBatis中的参数的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读! 当前题目:一文带你读懂MyBatis中的参数 分享URL:http://cdweb.net/article/jdsihh.html 其他资讯 SpringBoot有什么作用 AndroidUI绘制流程的示例分析 es6可不可以用双引号 怎么使用js的forEach方法实现数组遍历 xp系统创建虚拟目录的方法 18980820575 244261566 回到顶部 首页 关于快上网 服务范围 案例展示 解决方案 建站资讯 联系快上网 服务热线 18980820575
在这里有个很关键的params,这个参数类型为Map ,他会根据接口方法按顺序记录下接口参数的定义的名字,如果使用@Param指定了名字,就会记录这个名字,如果没有记录,那么就会使用它的序号作为名字。
Map
例如有如下接口:
List select(@Param('sex')String sex,Integer age);
那么他对应的params如下:
{ 0:'sex', 1:'1' }
继续看上面的convertArgsToSqlCommandParam方法,这里简要说明3种情况:
注意:从第3种情况来看,建议各位有多个入参的时候通过@Param指定参数名,方便后面(动态sql)的使用。
经过上面方法的处理后,在MapperMethod中会继续往下调用命名空间方式的方法:
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectList(command.getName(), param);
从这之后开始按照统一的方式继续处理入参。
处理集合
不管是selectOne还是selectMap方法,归根结底都是通过selectList进行查询的,不管是delete还是insert方法,都是通过update方法操作的。在selectList和update中所有参数的都进行了统一的处理。
在DefaultSqlSession.java中的wrapCollection方法:
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; }这里特别需要注意的一个地方是map.put("collection", object) ,这个设计是为了支持Set类型,需要等到MyBatis 3.3.0版本才能使用。wrapCollection处理的是只有一个参数时,集合和数组的类型转换成Map2类型,并且有默认的Key,从这里你能大概看到为什么中默认情况下写的array和list(Map类型没有默认值map)。参数的使用参数的使用分为两部分:第一种就是常见#{username}或者${username} 。第二种就是在动态SQL中作为条件,例如 。 下面对这两种进行详细讲解,为了方便理解,先讲解第二种情况。在动态SQL条件中使用参数关于动态SQL的基础内容可以查看官方文档。动态SQL为什么会处理参数呢?主要是因为动态SQL中的 , , 都会用到表达式,表达式中会用到属性名,属性名对应的属性值如何获取呢?获取方式就在这关键的一步。不知道多少人遇到Could not get property xxx from xxxClass或: Parameter ‘xxx' not found. Available parameters are[…] ,都是不懂这里引起的。在DynamicContext.java中,从构造方法看起:public DynamicContext(Configuration configuration, Object parameterObject) { if (parameterObject != null && !(parameterObject instanceof Map)) { MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject); } else { bindings = new ContextMap(null); } bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId()); }这里的Object parameterObject就是我们经过前面两步处理后的参数。这个参数经过前面两步处理后,到这里的时候,他只有下面三种情况:null,如果没有入参或者入参是null,到这里也是null。Map类型,除了null之外,前面两步主要是封装成Map类型。数组、集合和Map以外的Object类型,可以是基本类型或者实体类。看上面构造方法,如果参数是1,2情况时,执行代码bindings = new ContextMap(null);参数是3情况时执行if中的代码。我们看看ContextMap类,这是一个内部静态类,代码如下:static class ContextMap extends HashMap { private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) { this.parameterMetaObject = parameterMetaObject; } public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { // issue #61 do not modify the context when reading return parameterMetaObject.getValue(strKey); } return null; } }我们先继续看DynamicContext的构造方法,在if/else之后还有两行:bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());其中两个Key分别为:public static final String PARAMETER_OBJECT_KEY = "_parameter"; public static final String DATABASE_ID_KEY = "_databaseId";也就是说1,2两种情况的时候,参数值只存在于"_parameter"的键值中。3情况的时候,参数值存在于"_parameter"的键值中,也存在于bindings本身。当动态SQL取值的时候会通过OGNL从bindings中获取值。MyBatis在OGNL中注册了ContextMap:static { OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor()); }当从ContextMap取值的时候,会执行ContextAccessor中的如下方法:@Override public Object getProperty(Map context, Object target, Object name) throws OgnlException { Map map = (Map) target; Object result = map.get(name); if (map.containsKey(name) || result != null) { return result; } Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } return null; }参数中的target就是ContextMap类型的,所以可以直接强转为Map类型。参数中的name就是我们写在动态SQL中的属性名。下面举例说明这三种情况:1、null的时候:不管name是什么(name="_databaseId"除外,可能会有值),此时Object result = map.get(name);得到的result=null。在Object parameterObject = map.get(PARAMETER_OBJECT_KEY);中parameterObject=null,因此最后返回的结果是null。在这种情况下,不管写什么样的属性,值都会是null,并且不管属性是否存在,都不会出错。2、Map类型:此时Object result = map.get(name);一般也不会有值,因为参数值只存在于"_parameter"的键值中。然后到Object parameterObject = map.get(PARAMETER_OBJECT_KEY); ,此时获取到我们的参数值。在从参数值((Map)parameterObject).get(name)根据name来获取属性值。在这一步的时候,如果name属性不存在,就会报错:throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());name属性是什么呢,有什么可选值呢?这就是处理接口形式的入参和处理集合处理后所拥有的Key。如果你遇到过类似异常,相信看到这儿就明白原因了。3、数组、集合和Map以外的Object类型:这种类型经过了下面的处理:MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject);MetaObject是MyBatis的一个反射类,可以很方便的通过getValue方法获取对象的各种属性(支持集合数组和Map,可以多级属性点.访问,如user.username,user.roles[1].rolename) 。现在分析这种情况。首先通过name获取属性时Object result = map.get(name); ,根据上面ContextMap类中的get方法:public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { return parameterMetaObject.getValue(strKey); } return null; }可以看到这里会优先从Map中取该属性的值,如果不存在,那么一定会执行到下面这行代码:return parameterMetaObject.getValue(strKey)如果name刚好是对象的一个属性值,那么通过MetaObject反射可以获取该属性值。如果该对象不包含name属性的值,就会报错:throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + t.toString(), t);理解这三种情况后,使用动态SQL应该不会有参数名方面的问题了。在SQL语句中使用参数SQL中的两种形式#{username}或者${username} ,虽然看着差不多,但是实际处理过程差别很大,而且很容易出现莫名其妙的错误。${username}的使用方式为OGNL方式获取值,和上面的动态SQL一样,这里先说这种情况。${propertyName}参数在TextSqlNode.java中有一个内部的静态类BindingTokenParser,现在只看其中的handleToken方法:@Override public String handleToken(String content) { Object parameter = context.getBindings().get("_parameter"); if (parameter == null) { context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { context.getBindings().put("value", parameter); } Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" checkInjection(srtValue); return srtValue; }从put("value"这个地方可以看出来,MyBatis会创建一个默认为"value"的值,也就是说,在xml中的SQL中可以直接使用${value},从else if可以看出来,只有是简单类型的时候,才会有值。关于这点,举个简单例子,如果接口为List selectOrderby(String column) ,如果xml内容为: select * from user order by ${value} 这种情况下,虽然没有指定一个value属性,但是MyBatis会自动把参数column赋值进去。再往下的代码:Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value));这里和动态SQL就一样了,通过OGNL方式来获取值。看到这里使用OGNL这种方式时,你有没有别的想法?特殊用法:你是否在SQL查询中使用过某些固定的码值?一旦码值改变的时候需要改动很多地方,但是你又不想把码值作为参数传进来,怎么解决呢?你可能已经明白了。就是通过OGNL的方式,例如有如下一个码值类:package com.abel533.mybatis; public interface Code{ public static final String ENABLE = "1"; public static final String DISABLE = "0"; }如果在xml,可以这么使用: select * from user where enable = ${@com.abel533.mybatis.Code@ENABLE} 除了码值之外,你可以使用OGNL支持的各种方法,如调用静态方法。#{propertyName}参数这种方式比较简单,复杂属性的时候使用的MyBatis的MetaObject。在DefaultParameterHandler.java中:public void setParameters(PreparedStatement ps) throws SQLException { 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(); } typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } }上面这段代码就是从参数中取#{propertyName}值的方法,这段代码的主要逻辑就是if/else判断的地方,单独拿出来分析: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); }首先看第一个if,当使用的时候,MyBatis会自动生成额外的动态参数,如果propertyName是动态参数,就会从动态参数中取值。第二个if,如果参数是null,不管属性名是什么,都会返回null。第三个if,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。最后一个else,这种情况下是复杂对象或者Map类型,通过反射方便的取值。下面我们说明上面四种情况下的参数名注意事项。1、动态参数,这里的参数名和值都由MyBatis动态生成的,因此我们没法直接接触,也不需要管这儿的命名。但是我们可以了解一下这儿的命名规则,当以后错误信息看到的时候,我们可以确定出错的地方。在ForEachSqlNode.java中:private static String itemizeItem(String item, int i) { return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString(); }其中ITEM_PRFIX为public static final String ITEM_PREFIX = "__frch_"; 。如果在中的collection="userList" item="user" , 那么对userList循环产生的动态参数名就是:__frch_user_0,__frch_user_1,__frch_user_2…如果访问动态参数的属性,如user.username会被处理成__frch_user_0.username ,这种参数值的处理过程在更早之前解析SQL的时候就已经获取了对应的参数值。具体内容看下面有关的详细内容。2、参数为null,由于这里的判断和参数名无关,因此入参null的时候,在xml中写的#{name}不管name写什么,都不会出错,值都是null。3、可以直接使用typeHandler处理的类型。最常见的就是基本类型,例如有这样一个接口方法User selectById(@Param("id")Integer id) ,在xml中使用id的时候,我们可以随便使用属性名,不管用什么样的属性名,值都是id。4、复杂对象或者Map类型一般都是我们需要注意的地方,这种情况下,就必须保证入参包含这些属性,如果没有就会报错。这一点和可以参考上面有关MetaObject的地方。详解所有动态SQL类型中, 似乎是遇到问题最多的一个。例如有下面的方法: INSERT INTO user(username,password) VALUES (#{user.username},#{user.password}) 对应的接口:int insertUserList(@Param("userList")List list);我们通过foreach源码,看看MyBatis如何处理上面这个例子。在ForEachSqlNode.java中的apply方法中的前两行:Map bindings = context.getBindings(); final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);这里的bindings参数熟悉吗?上面提到过很多。经过一系列的参数处理后,这儿的bindings如下:{ "_parameter":{ "param1":list, "userList":list }, "_databaseId":null, }collectionExpression就是collection="userList"的值userList。我们看看evaluator.evaluateIterable如何处理这个参数,在ExpressionEvaluator.java中的evaluateIterable方法:public Iterable<?> evaluateIterable(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value == null) { throw new BuilderException("The expression '" + expression + "' evaluated to a null value."); } if (value instanceof Iterable) { return (Iterable<?>) value; } if (value.getClass().isArray()) { int size = Array.getLength(value); List answer = new ArrayList(); for (int i = 0; i < size; i++) { Object o = Array.get(value, i); answer.add(o); } return answer; } if (value instanceof Map) { return ((Map) value).entrySet(); } throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable."); }首先通过看第一行代码:Object value = OgnlCache.getValue(expression, parameterObject);这里通过OGNL获取到了userList的值。获取userList值的时候可能出现异常,具体可以参考上面动态SQL部分的内容。userList的值分四种情况。value == null,这种情况直接抛出异常BuilderException。value instanceof Iterable,实现Iterable接口的直接返回,如Collection的所有子类,通常是List。value.getClass().isArray()数组的情况,这种情况会转换为List返回。value instanceof Map如果是Map,通过((Map) value).entrySet()返回一个Set类型的参数。通过上面处理后,返回的值,是一个Iterable类型的值,这个值可以使用for (Object o : iterable)这种形式循环。在ForEachSqlNode中对iterable循环的时候,有一段需要关注的代码:if (o instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapEntry = (Map.Entry) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { applyIndex(context, i, uniqueNumber); applyItem(context, o, uniqueNumber); }如果是通过((Map) value).entrySet()返回的Set,那么循环取得的子元素都是Map.Entry类型,这个时候会将mapEntry.getKey()存储到index中,mapEntry.getValue()存储到item中。如果是List,那么会将序号i存到index中,mapEntry.getValue()存储到item中。常见错误补充当collection="userList"的值userList中的User是一个继承自Map的类型时,你需要保证循环中用到的所有对象的属性必须存在,Map类型存在的问题通常是,如果某个值是null,一般是不存在相应的key,这种情况会导致出错,会报找不到__frch_user_x参数。所以这种情况下,就是值是null,你也需要map.put(key,null) 。看完上述内容,你们掌握一文带你读懂MyBatis中的参数的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读! 当前题目:一文带你读懂MyBatis中的参数 分享URL:http://cdweb.net/article/jdsihh.html 其他资讯 SpringBoot有什么作用 AndroidUI绘制流程的示例分析 es6可不可以用双引号 怎么使用js的forEach方法实现数组遍历 xp系统创建虚拟目录的方法 18980820575 244261566 回到顶部 首页 关于快上网 服务范围 案例展示 解决方案 建站资讯 联系快上网 服务热线 18980820575
这里特别需要注意的一个地方是map.put("collection", object) ,这个设计是为了支持Set类型,需要等到MyBatis 3.3.0版本才能使用。
map.put("collection", object)
wrapCollection处理的是只有一个参数时,集合和数组的类型转换成Map2类型,并且有默认的Key,从这里你能大概看到为什么中默认情况下写的array和list(Map类型没有默认值map)。
参数的使用
参数的使用分为两部分:
#{username}
${username}
下面对这两种进行详细讲解,为了方便理解,先讲解第二种情况。
在动态SQL条件中使用参数
关于动态SQL的基础内容可以查看官方文档。
动态SQL为什么会处理参数呢?
主要是因为动态SQL中的 , , 都会用到表达式,表达式中会用到属性名,属性名对应的属性值如何获取呢?获取方式就在这关键的一步。不知道多少人遇到Could not get property xxx from xxxClass或: Parameter ‘xxx' not found. Available parameters are[…] ,都是不懂这里引起的。
Parameter ‘xxx' not found. Available parameters are[…]
在DynamicContext.java中,从构造方法看起:
public DynamicContext(Configuration configuration, Object parameterObject) { if (parameterObject != null && !(parameterObject instanceof Map)) { MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject); } else { bindings = new ContextMap(null); } bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId()); }
这里的Object parameterObject就是我们经过前面两步处理后的参数。这个参数经过前面两步处理后,到这里的时候,他只有下面三种情况:
看上面构造方法,如果参数是1,2情况时,执行代码bindings = new ContextMap(null);参数是3情况时执行if中的代码。我们看看ContextMap类,这是一个内部静态类,代码如下:
bindings = new ContextMap(null);
static class ContextMap extends HashMap { private MetaObject parameterMetaObject; public ContextMap(MetaObject parameterMetaObject) { this.parameterMetaObject = parameterMetaObject; } public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { // issue #61 do not modify the context when reading return parameterMetaObject.getValue(strKey); } return null; } }
我们先继续看DynamicContext的构造方法,在if/else之后还有两行:
bindings.put(PARAMETER_OBJECT_KEY, parameterObject); bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
其中两个Key分别为:
public static final String PARAMETER_OBJECT_KEY = "_parameter"; public static final String DATABASE_ID_KEY = "_databaseId";
也就是说1,2两种情况的时候,参数值只存在于"_parameter"的键值中。3情况的时候,参数值存在于"_parameter"的键值中,也存在于bindings本身。
"_parameter"
当动态SQL取值的时候会通过OGNL从bindings中获取值。MyBatis在OGNL中注册了ContextMap:
static { OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor()); }
当从ContextMap取值的时候,会执行ContextAccessor中的如下方法:
@Override public Object getProperty(Map context, Object target, Object name) throws OgnlException { Map map = (Map) target; Object result = map.get(name); if (map.containsKey(name) || result != null) { return result; } Object parameterObject = map.get(PARAMETER_OBJECT_KEY); if (parameterObject instanceof Map) { return ((Map)parameterObject).get(name); } return null; }
参数中的target就是ContextMap类型的,所以可以直接强转为Map类型。
参数中的name就是我们写在动态SQL中的属性名。
下面举例说明这三种情况:
1、null的时候:
不管name是什么(name="_databaseId"除外,可能会有值),此时Object result = map.get(name);得到的result=null。
Object result = map.get(name);
result=null
在Object parameterObject = map.get(PARAMETER_OBJECT_KEY);中parameterObject=null,因此最后返回的结果是null。
Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
parameterObject=null
在这种情况下,不管写什么样的属性,值都会是null,并且不管属性是否存在,都不会出错。
2、Map类型:
此时Object result = map.get(name);一般也不会有值,因为参数值只存在于"_parameter"的键值中。
然后到Object parameterObject = map.get(PARAMETER_OBJECT_KEY); ,此时获取到我们的参数值。
在从参数值((Map)parameterObject).get(name)根据name来获取属性值。
((Map)parameterObject).get(name)
在这一步的时候,如果name属性不存在,就会报错:
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
name属性是什么呢,有什么可选值呢?这就是处理接口形式的入参和处理集合处理后所拥有的Key。
如果你遇到过类似异常,相信看到这儿就明白原因了。
3、数组、集合和Map以外的Object类型:
这种类型经过了下面的处理:
MetaObject metaObject = configuration.newMetaObject(parameterObject); bindings = new ContextMap(metaObject);
MetaObject是MyBatis的一个反射类,可以很方便的通过getValue方法获取对象的各种属性(支持集合数组和Map,可以多级属性点.访问,如user.username,user.roles[1].rolename) 。
user.username,user.roles[1].rolename)
现在分析这种情况。
首先通过name获取属性时Object result = map.get(name); ,根据上面ContextMap类中的get方法:
public Object get(Object key) { String strKey = (String) key; if (super.containsKey(strKey)) { return super.get(strKey); } if (parameterMetaObject != null) { return parameterMetaObject.getValue(strKey); } return null; }
可以看到这里会优先从Map中取该属性的值,如果不存在,那么一定会执行到下面这行代码:
return parameterMetaObject.getValue(strKey)
如果name刚好是对象的一个属性值,那么通过MetaObject反射可以获取该属性值。如果该对象不包含name属性的值,就会报错:
throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + t.toString(), t);
理解这三种情况后,使用动态SQL应该不会有参数名方面的问题了。
在SQL语句中使用参数
SQL中的两种形式#{username}或者${username} ,虽然看着差不多,但是实际处理过程差别很大,而且很容易出现莫名其妙的错误。
${username}的使用方式为OGNL方式获取值,和上面的动态SQL一样,这里先说这种情况。
${propertyName}参数
在TextSqlNode.java中有一个内部的静态类BindingTokenParser,现在只看其中的handleToken方法:
@Override public String handleToken(String content) { Object parameter = context.getBindings().get("_parameter"); if (parameter == null) { context.getBindings().put("value", null); } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) { context.getBindings().put("value", parameter); } Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null" checkInjection(srtValue); return srtValue; }
从put("value"这个地方可以看出来,MyBatis会创建一个默认为"value"的值,也就是说,在xml中的SQL中可以直接使用${value},从else if可以看出来,只有是简单类型的时候,才会有值。
put("value"
"value"
关于这点,举个简单例子,如果接口为List selectOrderby(String column) ,如果xml内容为:
List selectOrderby(String column)
select * from user order by ${value}
这种情况下,虽然没有指定一个value属性,但是MyBatis会自动把参数column赋值进去。
再往下的代码:
Object value = OgnlCache.getValue(content, context.getBindings()); String srtValue = (value == null ? "" : String.valueOf(value));
这里和动态SQL就一样了,通过OGNL方式来获取值。
看到这里使用OGNL这种方式时,你有没有别的想法?
特殊用法:你是否在SQL查询中使用过某些固定的码值?一旦码值改变的时候需要改动很多地方,但是你又不想把码值作为参数传进来,怎么解决呢?你可能已经明白了。
就是通过OGNL的方式,例如有如下一个码值类:
package com.abel533.mybatis; public interface Code{ public static final String ENABLE = "1"; public static final String DISABLE = "0"; }
如果在xml,可以这么使用:
select * from user where enable = ${@com.abel533.mybatis.Code@ENABLE}
除了码值之外,你可以使用OGNL支持的各种方法,如调用静态方法。
#{propertyName}参数
这种方式比较简单,复杂属性的时候使用的MyBatis的MetaObject。
在DefaultParameterHandler.java中:
public void setParameters(PreparedStatement ps) throws SQLException { 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(); } typeHandler.setParameter(ps, i + 1, value, jdbcType); } } } }
上面这段代码就是从参数中取#{propertyName}值的方法,这段代码的主要逻辑就是if/else判断的地方,单独拿出来分析:
#{propertyName}
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); }
首先看第一个if,当使用的时候,MyBatis会自动生成额外的动态参数,如果propertyName是动态参数,就会从动态参数中取值。
第二个if,如果参数是null,不管属性名是什么,都会返回null。
第三个if,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。
最后一个else,这种情况下是复杂对象或者Map类型,通过反射方便的取值。
下面我们说明上面四种情况下的参数名注意事项。
1、动态参数,这里的参数名和值都由MyBatis动态生成的,因此我们没法直接接触,也不需要管这儿的命名。但是我们可以了解一下这儿的命名规则,当以后错误信息看到的时候,我们可以确定出错的地方。在ForEachSqlNode.java中:
private static String itemizeItem(String item, int i) { return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString(); }
其中ITEM_PRFIX为public static final String ITEM_PREFIX = "__frch_"; 。
public static final String ITEM_PREFIX = "__frch_";
如果在中的collection="userList" item="user" , 那么对userList循环产生的动态参数名就是:
collection="userList" item="user"
__frch_user_0,__frch_user_1,__frch_user_2…
如果访问动态参数的属性,如user.username会被处理成__frch_user_0.username ,这种参数值的处理过程在更早之前解析SQL的时候就已经获取了对应的参数值。具体内容看下面有关的详细内容。
user.username
__frch_user_0.username
2、参数为null,由于这里的判断和参数名无关,因此入参null的时候,在xml中写的#{name}不管name写什么,都不会出错,值都是null。
#{name}
3、可以直接使用typeHandler处理的类型。最常见的就是基本类型,例如有这样一个接口方法User selectById(@Param("id")Integer id) ,在xml中使用id的时候,我们可以随便使用属性名,不管用什么样的属性名,值都是id。
User selectById(@Param("id")Integer id)
4、复杂对象或者Map类型一般都是我们需要注意的地方,这种情况下,就必须保证入参包含这些属性,如果没有就会报错。这一点和可以参考上面有关MetaObject的地方。
详解
所有动态SQL类型中, 似乎是遇到问题最多的一个。
例如有下面的方法:
INSERT INTO user(username,password) VALUES (#{user.username},#{user.password})
对应的接口:
int insertUserList(@Param("userList")List list);
我们通过foreach源码,看看MyBatis如何处理上面这个例子。
在ForEachSqlNode.java中的apply方法中的前两行:
Map bindings = context.getBindings(); final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
这里的bindings参数熟悉吗?上面提到过很多。经过一系列的参数处理后,这儿的bindings如下:
{ "_parameter":{ "param1":list, "userList":list }, "_databaseId":null, }
collectionExpression就是collection="userList"的值userList。
collection="userList"
我们看看evaluator.evaluateIterable如何处理这个参数,在ExpressionEvaluator.java中的evaluateIterable方法:
evaluator.evaluateIterable
public Iterable<?> evaluateIterable(String expression, Object parameterObject) { Object value = OgnlCache.getValue(expression, parameterObject); if (value == null) { throw new BuilderException("The expression '" + expression + "' evaluated to a null value."); } if (value instanceof Iterable) { return (Iterable<?>) value; } if (value.getClass().isArray()) { int size = Array.getLength(value); List answer = new ArrayList(); for (int i = 0; i < size; i++) { Object o = Array.get(value, i); answer.add(o); } return answer; } if (value instanceof Map) { return ((Map) value).entrySet(); } throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable."); }首先通过看第一行代码:Object value = OgnlCache.getValue(expression, parameterObject);这里通过OGNL获取到了userList的值。获取userList值的时候可能出现异常,具体可以参考上面动态SQL部分的内容。userList的值分四种情况。value == null,这种情况直接抛出异常BuilderException。value instanceof Iterable,实现Iterable接口的直接返回,如Collection的所有子类,通常是List。value.getClass().isArray()数组的情况,这种情况会转换为List返回。value instanceof Map如果是Map,通过((Map) value).entrySet()返回一个Set类型的参数。通过上面处理后,返回的值,是一个Iterable类型的值,这个值可以使用for (Object o : iterable)这种形式循环。在ForEachSqlNode中对iterable循环的时候,有一段需要关注的代码:if (o instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapEntry = (Map.Entry) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { applyIndex(context, i, uniqueNumber); applyItem(context, o, uniqueNumber); }如果是通过((Map) value).entrySet()返回的Set,那么循环取得的子元素都是Map.Entry类型,这个时候会将mapEntry.getKey()存储到index中,mapEntry.getValue()存储到item中。如果是List,那么会将序号i存到index中,mapEntry.getValue()存储到item中。常见错误补充当collection="userList"的值userList中的User是一个继承自Map的类型时,你需要保证循环中用到的所有对象的属性必须存在,Map类型存在的问题通常是,如果某个值是null,一般是不存在相应的key,这种情况会导致出错,会报找不到__frch_user_x参数。所以这种情况下,就是值是null,你也需要map.put(key,null) 。看完上述内容,你们掌握一文带你读懂MyBatis中的参数的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读! 当前题目:一文带你读懂MyBatis中的参数 分享URL:http://cdweb.net/article/jdsihh.html 其他资讯 SpringBoot有什么作用 AndroidUI绘制流程的示例分析 es6可不可以用双引号 怎么使用js的forEach方法实现数组遍历 xp系统创建虚拟目录的方法 18980820575 244261566 回到顶部 首页 关于快上网 服务范围 案例展示 解决方案 建站资讯 联系快上网 服务热线 18980820575
首先通过看第一行代码:
Object value = OgnlCache.getValue(expression, parameterObject);
这里通过OGNL获取到了userList的值。获取userList值的时候可能出现异常,具体可以参考上面动态SQL部分的内容。
userList的值分四种情况。
value == null
value instanceof Iterable
value.getClass().isArray()
value instanceof Map
((Map) value).entrySet()
通过上面处理后,返回的值,是一个Iterable类型的值,这个值可以使用for (Object o : iterable)这种形式循环。
for (Object o : iterable)
在ForEachSqlNode中对iterable循环的时候,有一段需要关注的代码:
if (o instanceof Map.Entry) { @SuppressWarnings("unchecked") Map.Entry mapEntry = (Map.Entry) o; applyIndex(context, mapEntry.getKey(), uniqueNumber); applyItem(context, mapEntry.getValue(), uniqueNumber); } else { applyIndex(context, i, uniqueNumber); applyItem(context, o, uniqueNumber); }
如果是通过((Map) value).entrySet()返回的Set,那么循环取得的子元素都是Map.Entry类型,这个时候会将mapEntry.getKey()存储到index中,mapEntry.getValue()存储到item中。
Map.Entry
mapEntry.getKey()
mapEntry.getValue()
如果是List,那么会将序号i存到index中,mapEntry.getValue()存储到item中。
常见错误补充
当collection="userList"的值userList中的User是一个继承自Map的类型时,你需要保证循环中用到的所有对象的属性必须存在,Map类型存在的问题通常是,如果某个值是null,一般是不存在相应的key,这种情况会导致出错,会报找不到__frch_user_x参数。所以这种情况下,就是值是null,你也需要map.put(key,null) 。
__frch_user_x
map.put(key,null)
看完上述内容,你们掌握一文带你读懂MyBatis中的参数的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注创新互联行业资讯频道,感谢各位的阅读!