{"id":18812358,"url":"https://github.com/mozhu811/learning-mybatis","last_synced_at":"2025-07-13T18:34:32.878Z","repository":{"id":101711731,"uuid":"143211139","full_name":"mozhu811/learning-mybatis","owner":"mozhu811","description":"Mybatis学习笔记","archived":false,"fork":false,"pushed_at":"2019-03-09T07:38:47.000Z","size":21,"stargazers_count":94,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-13T21:12:38.121Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://cruii.io/archives/2019111907160575112","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mozhu811.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-01T21:37:51.000Z","updated_at":"2024-04-19T02:05:05.000Z","dependencies_parsed_at":null,"dependency_job_id":"fa4ed5fa-1dea-4ed9-b289-3faffd88b837","html_url":"https://github.com/mozhu811/learning-mybatis","commit_stats":null,"previous_names":["mozhu811/learning-mybatis"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mozhu811/learning-mybatis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozhu811%2Flearning-mybatis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozhu811%2Flearning-mybatis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozhu811%2Flearning-mybatis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozhu811%2Flearning-mybatis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mozhu811","download_url":"https://codeload.github.com/mozhu811/learning-mybatis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mozhu811%2Flearning-mybatis/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265187147,"owners_count":23724827,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T23:32:18.672Z","updated_at":"2025-07-13T18:34:32.842Z","avatar_url":"https://github.com/mozhu811.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"* [项目介绍](#项目介绍)\n* [Mybatis参数处理方式](#mybatis参数处理方式)\n  * [通常方式](#通常方式)\n  * [通过POJO传递](#通过pojo传递)\n  * [通过Map容器传递](#通过map容器传递)\n  * [通过DTO传递](#通过dto传递)\n  * [其他情况](#其他情况)\n* [源码分析Mybatis的参数处理过程](#源码分析mybatis的参数处理过程)\n  ​    \n## 项目介绍\n该repo为学习Mybatis时的记录,该文档主要记录Mybatis的相关知识和源码分析.\n\n* 2018年08月02日\n  * 更新[通过源码分析Mybatis的参数处理方式]\n* 待施工......\n\n## Mybatis参数处理方式\n### 通常方式\n1. 单个参数,mybatis不会做特殊处理   \n\\#{参数名}就可以取出参数值\n\n2. 多个参数,mybatis会做特殊处理  \n多个参数会封装成一个map  \nkey: param1,param2,param3...或者其他参数索引  \nvalue: 传入的值  \n\\#{} 就是从map找那个获取指定的key对应的value\n在mapper.xml文件中默认使用#{param1},#{param2}这样的方式来获取传入参数\n  \n3. 命名参数:明确的指定封装参数时map的key  \n使用@Param(key)注解  \n例如 Employee findByIdAndName(@Param(\"id\") Integer id, @Param(\"name\") String name);\n\n### 通过POJO传递\n如果多个参数是业务逻辑的数据类型,可以直接传入POJO  \n使用#{属性字段}来取出POJO的属性值\n\n### 通过Map容器传递\n如果多个参数没有对应的POJO,则可以直接使用Map  \n使用#{key}来取出对应的值\n\n```java\npublic interface EmployeeMapper{\n    /*...*/\n    // EmployeeMapper中定义方法\n    Employee getEmpByMap(Map\u003cString, Object\u003e map);\n}\n\n```\n然后在EmployeeMapper.xml文件中如下配置\n\n```xml\n\u003cselect id=\"getEmpByMap\" resultType=\"com.crzmy.entity.Employee\"\u003e\n    SELECT * FROM tb_employee WHERE emp_id = #{id} AND emp_name = #{name}\n\u003c/select\u003e\n```\n测试方法代码\n\n```java\n\npublic class AppTest{\n\t@Test\n\tpublic void test(){\n\t\t/*\n\t\t...\n\t\t*/\n\t\t// 测试方法中主要代码\n\t\tEmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);\n\t\tMap\u003cString, Object\u003e map = new HashMap\u003c\u003e();\n\t\tmap.put(\"id\",1);\n\t\tmap.put(\"name\", \"jack\");\n\t\tEmployee emp = mapper.getEmpByMap(map);\n\t\tSystem.out.println(emp);\n\t\t/*\n\t\t...\n\t\t*/\n    }\n}\n\n```\n### 通过DTO传递\n如果多个参数不是业务模型中的数据,但是经常使用,可以使用一个DTO即数据传输对象\n\n```java\n@Getter\n@Setter\n@NoArgsConstructor\n@AllArgsConstructor\npublic class EmployeeDTO{\n\tprivate Integer empId;\n\tprivate String empName;\n}\n```\n\n### 其他情况\n```java\npublic class EmployeeMapper{\n\t/**\n\t* 通过id和名字查找雇员\n\t* \n\t* 这种情况雇员id取值方式有1种\n\t* #{empId}或者#{param1}\n\t* 而雇员名字则只能通过1个方式\n\t* #{param2}\n\t* \n\t* @param empId 雇员id\n\t* @param empName 雇员名字\n\t* @return 雇员对象\n\t*/\n\tEmployee getEmp(@Param(\"empId\") Integer empId, String empName);\n    \n\t/**\n\t* 通过id和雇员信息更新对象\n\t* \n\t* 这种情况雇员id取值方式只有1种\n\t* #{param1}\n\t* 而雇员对象中的属性有2种方式,以雇员名字为例\n\t* #{param2.empName} 或者 #{emp.empName}\n\t* \n\t* @param empId  待更新的雇员id\n\t* @param emp    新的雇员对象\n\t* @return  是否更改成功\n\t*/\n\tboolean updateEmp(Integer empId, @Param(\"emp\") Employee emp);\n    \n\t/**\n\t* 通过一个雇员id列表批量查询雇员信息\n\t* \n\t* mybatis对Collection或者数组也会特殊处理\n\t* 把集合或者数组封装在Map中\n\t* \n\t* 如果是List集合也可以直接使用\"list\"这个key来获取\n\t* 比如取出list中的第一个id\n\t* #{list[0]}\n\t* \n\t* @param ids    雇员id列表\n\t* @return   雇员对象列表\n\t*/\n\tList\u003cEmployee\u003e getEmpsByIds(List\u003cInteger\u003e ids);\n}\n```\n## 源码分析Mybatis的参数处理过程\nMapper接口\n\n```java\npublic class EmployeeMapper{\n\t/**\n\t* 通过id,名字和性别查询雇员\n\t* @param id 雇员id\n\t* @param name   雇员名字\n\t* @param gender 雇员性别\n\t* @return   Employee对象\n\t*/\n\tEmployee findByIdAndNameWithGender(@Param(\"empId\") Integer id, @Param(\"empName\") String name, String gender);\n}\n```\n分析入口\n\n```java\npublic class AppTest{\n\t@Test\n\tpublic void test(){\n\t\t/*\n\t\t...\n\t\t*/\n\t\tEmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);\n\t\tEmployee employee = mapper.findByIdAndNameWithGender(1,\"cruii\",\"1\");\n\t\t/*\n\t\t...\n\t\t*/\n\t}\n}\n```\n第一行代码,mybatis会使用MapperProxyFactory类中的newInstance(MapperProxy\u003cT\u003e mapperProxy)方法来使用JDK动态代理生成EmployeeMapper代理对象  \n通过代理对象来与数据库进行会话. \n\n```java\npublic class MapperProxyFactory{\n\t/*\n\t...\n\t*/\n\tprotected T newInstance(MapperProxy\u003cT\u003e mapperProxy) {\n\t    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);\n\t}\n\t    \n\tpublic T newInstance(SqlSession sqlSession) {\n\t    final MapperProxy\u003cT\u003e mapperProxy = new MapperProxy\u003cT\u003e(sqlSession, mapperInterface, methodCache);\n\t    return newInstance(mapperProxy);\n\t}\n}\n```\nEmployee employee = mapper.findByIdAndNameWithGender(1,\"cruii\",\"1\");\n该行代码会调用代理对象的invoke方法\n\n```java\npublic class MapperProxy\u003cT\u003e implements InvocationHandler, Serializable {\n/*\n...\n */\n@Override\npublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n\ttry {\n\t\tif (Object.class.equals(method.getDeclaringClass())) {\n\t\t\treturn method.invoke(this, args);\n\t\t} else if (isDefaultMethod(method)) {\n\t\t\treturn invokeDefaultMethod(proxy, method, args);\n\t\t}\n\t} catch (Throwable t) {\n\t\tthrow ExceptionUtil.unwrapThrowable(t);\n\t}\n\t\n\tfinal MapperMethod mapperMethod = cachedMapperMethod(method);\n\treturn mapperMethod.execute(sqlSession, args);\n\t}\n\t\n\t/*\n\t...\n\t*/\n}\n```\n如果是Object类里的方法,如toString(),hashCode()等方法,则直接通过反射执行  \n\n\u003e MapperProxy是一个InvocationHandler,在使用JDK动态代理生成对象时使用,  \n\u003e 会根据该接口生成动态代理对象,然后利用反射调用实际对象的目标方法.  \n\u003e 然而动态代理对象里面的方法是有接口(interface)声明的.  \n\u003e 但是动态代理对象也能调用toString(),hashCode()等方法,而这些方法就是从Object类继承过来的.  \n\u003e 所以if (Object.class.equals(method.getDeclaringClass()))这行代码的作用就是:  \n\u003e 如果利用动态代理对象调用的是toString(),hashCode()等从Object类继承的方法,则直接反射调用.  \n\u003e 如果是接口声明的方法,则通过下面的MapperMethod执行.\n\n此时,传入的方法是com.crzmy.mapper.EmployeeMapper.findByIdAndNameWithGender  \n通过method.getDeclaringClass()得到的结果是interface com.crzmy.mapper.EmployeeMapper  \n所以直接通过MapperProxy类的cachedMapperMethod方法生成一个MapperMethod对象.\n\n```java\npublic class MapperProxy {\n\t/*\n\t...\n\t */\n\tprivate MapperMethod cachedMapperMethod(Method method) {\n\t\tMapperMethod mapperMethod = methodCache.get(method);\n\t\tif (mapperMethod == null) {\n\t\t\tmapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());\n\t\t\tmethodCache.put(method, mapperMethod);\n\t\t}\n\t\treturn mapperMethod;\n\t}\n\t/*\n\t...\n\t */\n}\n```\n\n该方法首先在缓存中查找是否存在目标方法,如果不存在,则创建一个新的MapperMethod对象并且缓存,每一个MapperMethod对象都代表了SQL映射文件mapper.xml里的一个SQL语句或者FLUSH配置,对应的SQL语句通过全类名和方法名从Configuration对象中获得.  \n这样当以后再次调用同一个mapper方法时直接返回缓存中的对象,不必再次创建,节省内存.  \n当获取了MapperMethod对象后,则通过该对象的execute方法执行目标方法.  \nMapperMethod类中有两个成员变量,SqlCommand对象和MethodSignature对象.  \n在创建MapperMethod对象时,会同时初始化这两个对象.  \n\n* SqlCommand类\n\n该类负责封装SQL语句的标签类型(如:SELECT,UPDATE,DELETE,INSERT)和目标方法名  \n\nSqlCommand类部分源码如下\n\n```java\npublic static class SqlCommand {\n\t/*\n\tname 负责存放调用的目标方法名  \n\ttype 负责存放SQL语句的类型\n\t*/\n\tprivate final String name;\n\tprivate final SqlCommandType type;\n\t\n\tpublic SqlCommand(Configuration configuration, Class\u003c?\u003e mapperInterface, Method method) {\n\t\tfinal String methodName = method.getName();\n\t\tfinal Class\u003c?\u003e declaringClass = method.getDeclaringClass();\n\t\tMappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,\n\t\t  configuration);\n\t\tif (ms == null) {\n\t\t\tif (method.getAnnotation(Flush.class) != null) {\n\t\t\t  name = null;\n\t\t\t  type = SqlCommandType.FLUSH;\n\t\t\t} else {\n\t\t\t  throw new BindingException(\"Invalid bound statement (not found): \"\n\t\t\t      + mapperInterface.getName() + \".\" + methodName);\n\t\t\t}\n\t\t} else {\n\t\t\t/*\n\t\t\t获取name和type\n\t\t\t*/\n\t\t\tname = ms.getId();\n\t\t\ttype = ms.getSqlCommandType();\n\t\t\tif (type == SqlCommandType.UNKNOWN) {\n\t\t\t  throw new BindingException(\"Unknown execution method for: \" + name);\n\t\t\t}\n\t\t}\n\t}\n\t    \n\t/*\n\t...\n\t*/\n}\n```\nMapperMethod中的resolveMappedStatement方法\n\n```java\npublic class MapperMethod{\n\n\t/*\n\t...\n\t*/\n\t \n\tprivate MappedStatement resolveMappedStatement(Class\u003c?\u003e mapperInterface, String methodName,\n\tClass\u003c?\u003e declaringClass, Configuration configuration) {\n\t\tString statementId = mapperInterface.getName() + \".\" + methodName;\n\t\tif (configuration.hasStatement(statementId)) {\n\t\t\treturn configuration.getMappedStatement(statementId);\n\t\t} else if (mapperInterface.equals(declaringClass)) {\n\t\t\treturn null;\n\t\t}\n\t\tfor (Class\u003c?\u003e superInterface : mapperInterface.getInterfaces()) {\n\t\t\tif (declaringClass.isAssignableFrom(superInterface)) {\n\t\t\t\tMappedStatement ms = resolveMappedStatement(superInterface, methodName,\n\t\t\t\t  declaringClass, configuration);\n\t\t\t\tif (ms != null) {\n\t\t\t\t\treturn ms;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\t    \n\t/*\n\t...\n\t*/\n}\n```\n在实例化SqlCommand的过程中,在构造方法里首先获取到目标方法的方法名以及目标方法声明的类对应的Class对象.  \n进入resolveMappedStatement方法,首先通过传入的EmployeeMapper接口的Class对象,获取全类名,然后拼接目标方法名组成statementId.再判断Configuration的mappedStatements中是否有对应的key,若true,则返回mappedStatements中对应key的MappedStatement对象.  \n回到SqlCommand的构造器中,此时MappedStatement ms已被赋值为对应的目标方法的MappedStatement对象.直接通过ms.getId()和ms.getSqlCommandType()方法获取目标方法名和SQL类型.  \n在本例中,即:  \nname: com.crzmy.mapper.EmployeeMapper.findByIdAndNameWithGender  \ntype: SELECT  \n至此SqlCommand对象初始化完毕.\n\n* MethodSignature类\n\n该类负责封装方法的参数和返回值类型等信息  \n\nMethodSignature部分源码\n\n```java\npublic static class MethodSignature {\n\t\n\tprivate final boolean returnsMany;\n\tprivate final boolean returnsMap;\n\tprivate final boolean returnsVoid;\n\tprivate final boolean returnsCursor;\n\tprivate final Class\u003c?\u003e returnType;\n\tprivate final String mapKey;\n\tprivate final Integer resultHandlerIndex;\n\tprivate final Integer rowBoundsIndex;\n\tprivate final ParamNameResolver paramNameResolver;\n\t\n\tpublic MethodSignature(Configuration configuration, Class\u003c?\u003e mapperInterface, Method method) {\n\t\t/*\n\t\t获取返回值类型\n\t\t*/\n\t\tType resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);\n\t\t  \n\t\t/*\n\t\t根据不同的返回值类型进行处理,并赋值给returnType\n\t\t*/\n\t\tif (resolvedReturnType instanceof Class\u003c?\u003e) {\n\t\t\tthis.returnType = (Class\u003c?\u003e) resolvedReturnType;\n\t\t} else if (resolvedReturnType instanceof ParameterizedType) {\n\t\t\tthis.returnType = (Class\u003c?\u003e) ((ParameterizedType) resolvedReturnType).getRawType();\n\t\t} else {\n\t\t\tthis.returnType = method.getReturnType();\n\t\t}\n\t\tthis.returnsVoid = void.class.equals(this.returnType);\n\t\tthis.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();\n\t\tthis.returnsCursor = Cursor.class.equals(this.returnType);\n\t\tthis.mapKey = getMapKey(method);\n\t\tthis.returnsMap = this.mapKey != null;\n\t\tthis.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);\n\t\tthis.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);\n\t\tthis.paramNameResolver = new ParamNameResolver(configuration, method);\n\t}\n\t/*\n\t...\n\t*/\n}\n```\nMethodSignature类的构造方法会首先调用TypeParameterResolver类的resolveReturnType方法来获取目标方法的返回值类型,传入的参数就是目标方法对应的Method对象和EmployeeMapper类对象  \n\nTypeParameterResolver类部分源码\n\n```java\npublic class TypeParameterResolver {\n\t/*\n\t...\n\t*/\n\t  \n\tpublic static Type resolveReturnType(Method method, Type srcType) {\n\t\tType returnType = method.getGenericReturnType();\n\t\tClass\u003c?\u003e declaringClass = method.getDeclaringClass();\n\t\treturn resolveType(returnType, srcType, declaringClass);\n\t}\n\t  \n\t/*\n\t...\n\t*/\n\t  \n\tprivate static Type resolveType(Type type, Type srcType, Class\u003c?\u003e declaringClass) {\n\t\tif (type instanceof TypeVariable) {\n\t\t\treturn resolveTypeVar((TypeVariable\u003c?\u003e) type, srcType, declaringClass);\n\t\t} else if (type instanceof ParameterizedType) {\n\t\t\treturn resolveParameterizedType((ParameterizedType) type, srcType, declaringClass);\n\t\t} else if (type instanceof GenericArrayType) {\n\t\t\treturn resolveGenericArrayType((GenericArrayType) type, srcType, declaringClass);\n\t\t} else {\n\t\t\treturn type;\n\t\t}\n\t}\n\t  \n\t/*\n\t...\n\t*/\n}\n```\nresolveReturnType直接获取到目标方法的返回值类型,在该例子中即class com.crzmy.entity.Employee  \n然后获取方法所属的类对象,即interface com.crzmy.mapper.EmployeeMapper  \n再进入resolveType方法,该方法判断返回值类型是否是TypeVariable(类型变量), ParameterizedType(参数化类型)或者GenericArrayType(泛型数组).\n\n\u003e void method(E e){}中的E就是类型变量  \n\u003e Map\u003cString, Integer\u003e map; map的Type就是ParameterizedType  \n\u003e List\u003cString\u003e[] list; list的Type就是GenericArrayType\n\n很显然,本例子中以上都不是,则直接返回class com.crzmy.entity.Employee.  \n然后在MethodSignature类的构造方法的最后一句\n\n```java\nthis.paramNameResolver = new ParamNameResolver(configuration, method);\n```\n该条语句实例化了一个ParamNameResolver类对象,该类主要的作用就是解析参数.\n\nParamNameResolver部分源码\n\n```java\npublic class ParamNameResolver {\n\n\tprivate static final String GENERIC_NAME_PREFIX = \"param\";\n\t\n\tprivate final SortedMap\u003cInteger, String\u003e names;\n\t\n\tprivate boolean hasParamAnnotation;\n\t\n\tpublic ParamNameResolver(Configuration config, Method method) {\n\t\t/*\n\t\t存储目标方法的参数对应的Class对象\n\t\t*/\n\t\tfinal Class\u003c?\u003e[] paramTypes = method.getParameterTypes();\n\t\t    \n\t\t/*\n\t\t存储目标方法的注解对象数组,每一个方法的参数都有一个注解数组\n\t\t*/\n\t\tfinal Annotation[][] paramAnnotations = method.getParameterAnnotations();\n\t\tfinal SortedMap\u003cInteger, String\u003e map = new TreeMap\u003cInteger, String\u003e();\n\t\t    \n\t\t/*\n\t\t存储目标方法的参数个数\n\t\t*/\n\t\tint paramCount = paramAnnotations.length;\n\t\t// get names from @Param annotations\n\t\tfor (int paramIndex = 0; paramIndex \u003c paramCount; paramIndex++) {\n\t\t\tif (isSpecialParameter(paramTypes[paramIndex])) {\n\t\t\t\t// skip special parameters\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tString name = null;\n\t\t\tfor (Annotation annotation : paramAnnotations[paramIndex]) {\n\t\t\t\tif (annotation instanceof Param) {\n\t\t\t\t\thasParamAnnotation = true;\n\t\t\t\t\tname = ((Param) annotation).value();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (name == null) {\n\t\t\t\t// @Param was not specified.\n\t\t\t\tif (config.isUseActualParamName()) {\n\t\t\t\t\tname = getActualParamName(method, paramIndex);\n\t\t\t\t}\n\t\t\t\tif (name == null) {\n\t\t\t\t\t// use the parameter index as the name (\"0\", \"1\", ...)\n\t\t\t\t\t// gcode issue #71\n\t\t\t\t\tname = String.valueOf(map.size());\n\t\t\t\t}\n\t\t\t}\n\t\t\tmap.put(paramIndex, name);\n\t\t}\n\t\tnames = Collections.unmodifiableSortedMap(map);\n\t}\n\t  \n\tprivate String getActualParamName(Method method, int paramIndex) {\n\t\tif (Jdk.parameterExists) {\n\t\t  return ParamNameUtil.getParamNames(method).get(paramIndex);\n\t\t}\n\t\treturn null;\n\t}\n\t  \n\t/*\n\t...\n\t*/\n \n}\n```\nParamNameUtil工具类源码\n\n```java\n@UsesJava8\npublic class ParamNameUtil {\n\tpublic static List\u003cString\u003e getParamNames(Method method) {\n\t\treturn getParameterNames(method);\n\t}\n\t\n\tpublic static List\u003cString\u003e getParamNames(Constructor\u003c?\u003e constructor) {\n\t\treturn getParameterNames(constructor);\n\t}\n\t\n\tprivate static List\u003cString\u003e getParameterNames(Executable executable) {\n\t\tfinal List\u003cString\u003e names = new ArrayList\u003cString\u003e();\n\t\tfinal Parameter[] params = executable.getParameters();\n\t\tfor (Parameter param : params) {\n\t\t\tnames.add(param.getName());\n\t\t}\n\t\treturn names;\n\t}\n\t\n\tprivate ParamNameUtil() {\n\t\tsuper();\n\t}\n}\n```\n\n通过isSpecialParameter(paramTypes[paramIndex])判断是否是RowBounds和ResultHandler特殊类型,如果true,则跳过.  \n紧接着判断每一个注解是否是Param注解,如果true,则hasParamAnnotation赋值为true表示该方法有@Param注解,然后直接把Param注解的value值赋值给name,在本例子中即empId和empName.  \n如果没有使用@Param注解,则判断是否开启了useActualParamName,  \n\n* 如果为true,则调用getActualParamName方法,并通过ParamNameUtil工具类获取目标方法的参数名,再把参数名存储到List中,接着根据传入的索引获取对应的参数名.然后把参数索引和参数名存放到map中.  \n\n* 如果为false,那么就会使用参数索引作为name.  \n\n当所有参数都判断之后,通过Collections.unmodifiableSortedMap(map)返回一个只读的Map容器赋值给names,同样存放着参数索引和参数名的映射关系,即:  \n当useActualParamName()为true时:  \n0 -\u003e \"id\"  \n1 -\u003e \"name\"  \n2 -\u003e \"gender\"  \n\n当useActualParamName()为false时:  \n0 -\u003e \"id\"  \n1 -\u003e \"name\"  \n2 -\u003e 2\n\n\u003e 补充  \n\u003e 从JDK8开始,可以通过打开javac -parameters,然后通过method.getParameters()获取到参数的名称.  \n\u003e 上面代码中的Executable对象就是Java的方法Method类和构造器Constructor类的父类,拥有getParameters()方法.\n\u003e 在本例中即:id,name,gender  \n\u003e 如果是JDK7及以下,则获取到的是arg0,arg1,arg2等无意义的参数名.\n\n\u003cstrong\u003e本例默认是useActualParamName()为false,所以names的存储情况为第二种情况\u003c/strong\u003e  \n\n至此ParamNameResolver对象实例化完成,然后赋值给MethodSignature的paramNameResolver变量.紧接着MethodSignature对象也实例化完成,同时MapperMethod也初始化完成,最后通过methodCache.put(method, mapperMethod);将mapperMethod对象缓存,存储的是目标方法的Method对象和mapperMethod的映射.  \n现在就正式开始进入MapperMethod的execute方法,该方法执行对应的SQL语句并且根据返回值类型返回值.\n\nMapperMethod类execute方法\n\n```java\npublic class MapperMethod {\n\tprivate final SqlCommand command;\n\tprivate final MethodSignature method;\n\t\n\tpublic MapperMethod(Class\u003c?\u003e mapperInterface, Method method, Configuration config) {\n\t\tthis.command = new SqlCommand(config, mapperInterface, method);\n\t\tthis.method = new MethodSignature(config, mapperInterface, method);\n\t}\n\t\n\tpublic Object execute(SqlSession sqlSession, Object[] args) {\n\t\tObject result;\n\t\tswitch (command.getType()) {\n\t\t\tcase INSERT: {\n\t\t\t\tObject param = method.convertArgsToSqlCommandParam(args);\n\t\t\t\tresult = rowCountResult(sqlSession.insert(command.getName(), param));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase UPDATE: {\n\t\t\t\tObject param = method.convertArgsToSqlCommandParam(args);\n\t\t\t\tresult = rowCountResult(sqlSession.update(command.getName(), param));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase DELETE: {\n\t\t\t\tObject param = method.convertArgsToSqlCommandParam(args);\n\t\t\t\tresult = rowCountResult(sqlSession.delete(command.getName(), param));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SELECT:\n\t\t\t\tif (method.returnsVoid() \u0026\u0026 method.hasResultHandler()) {\n\t\t\t\t\texecuteWithResultHandler(sqlSession, args);\n\t\t\t\t\tresult = null;\n\t\t\t\t} else if (method.returnsMany()) {\n\t\t\t\t\tresult = executeForMany(sqlSession, args);\n\t\t\t\t} else if (method.returnsMap()) {\n\t\t\t\t\tresult = executeForMap(sqlSession, args);\n\t\t\t\t} else if (method.returnsCursor()) {\n\t\t\t\t\tresult = executeForCursor(sqlSession, args);\n\t\t\t\t} else {\n\t\t\t\t\tObject param = method.convertArgsToSqlCommandParam(args);\n\t\t\t\t\tresult = sqlSession.selectOne(command.getName(), param);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase FLUSH:\n\t\t\t\tresult = sqlSession.flushStatements();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tthrow new BindingException(\"Unknown execution method for: \" + command.getName());\n\t\t}\n\t\tif (result == null \u0026\u0026 method.getReturnType().isPrimitive() \u0026\u0026 !method.returnsVoid()) {\n\t\t\tthrow new BindingException(\"Mapper method '\" + command.getName() \n\t\t\t+ \" attempted to return null from a method with a primitive return type (\" + method.getReturnType() + \").\");\n\t\t}\n\t\treturn result;\n\t}\n\t  \n\t/*\n\t...\n\t*/\n}\n```\n首先通过SqlCommand的实例化对象command获取SQL类型,在本例中即SELECT,进入对应的case语句块.  \n其次根据MethodSignature的实例化对象method存储的目标方法的返回值类型判断,在本例中返回值为一个Employee对象,所以直接到达Object param = method.convertArgsToSqlCommandParam(args);  \n然后进入method.convertArgsToSqlCommandParam方法\n\n```java\npublic static class MethodSignature {\n\t/*\n\t...\n\t*/\n\t\t\n\tpublic Object convertArgsToSqlCommandParam(Object[] args) {\n\t\treturn paramNameResolver.getNamedParams(args);\n\t}\n\t\t\n\t/*\n\t...\n\t*/\n}\n```\n该方法又调用了ParamNameResolver的getNamedParams方法,该方法是解析参数的核心方法,进入该方法\n\n```java\npublic class ParamNameResolver {\n\tprivate static final String GENERIC_NAME_PREFIX = \"param\";\n\t\n\tprivate final SortedMap\u003cInteger, String\u003e names;\n\t\n\tprivate boolean hasParamAnnotation;\n\t\t\n\t/*\n\t...\n\t*/\n\t\t\n\tpublic Object getNamedParams(Object[] args) {\n\t\tfinal int paramCount = names.size();\n\t\tif (args == null || paramCount == 0) {\n\t\t\treturn null;\n\t\t} else if (!hasParamAnnotation \u0026\u0026 paramCount == 1) {\n\t\t\treturn args[names.firstKey()];\n\t\t} else {\n\t\t\tfinal Map\u003cString, Object\u003e param = new ParamMap\u003cObject\u003e();\n\t\t\tint i = 0;\n\t\t\tfor (Map.Entry\u003cInteger, String\u003e entry : names.entrySet()) {\n\t\t\t\tparam.put(entry.getValue(), args[entry.getKey()]);\n\t\t\t\t// add generic param names (param1, param2, ...)\n\t\t\t\tfinal String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);\n\t\t\t\t// ensure not to overwrite parameter named with @Param\n\t\t\t\tif (!names.containsValue(genericParamName)) {\n\t\t\t\t\tparam.put(genericParamName, args[entry.getKey()]);\n\t\t\t\t}\n\t\t\t\ti++;\n\t\t\t}\n\t\t\treturn param;\n\t\t}\n\t}\n}\n```\n调用的目标方法\n\n```java\nEmployee findByIdAndNameWithGender(@Param(\"empId\") Integer id, @Param(\"empName\") String name, String gender);\n```\n此时传入的参数args的内容是\n\n```text\n1,\"cruii\",\"1\"\n```\n而names存储的内容是\n\n```text\n0 -\u003e \"id\"  \n1 -\u003e \"name\"  \n2 -\u003e 2 \n```\n先来看只有一个参数的情况  \n假设现在传入的参数args数组里只有一个Integer类型的1,没有使用@Param注解,并且names存储的只有一个\"0\"-\u003e\"id\"映射.  \n则\n\n```java\nreturn args[names.firstKey()];\n\n/* \n则相当于  \nreturn args[0];返回 1 \n*/\n```\n\n\n如果使用了@Param注解,则遍历Map容器names,以value作为key和args数组对应索引的值作为value存储到Map容器param中  \n此时相当于\n\n```java  \nparam.put(\"id\", 1);\n```\n然后返回param.\n再回到原来的例子,多个参数情况  \n同样遍历Map容器names,以value作为key和args数组对应索引的值作为value存储到Map容器param中.并且会根据GENERIC_NAME_PREFIX常量即\"param\"和当前的索引拼装成新的字符串,即param1,param2,...,paramN.然后和args数组里对应索引的值存储到Map容器param中,最后遍历完成后param的存储情况是:\n\n```text\n\"id\" -\u003e 1\n\"param1\" -\u003e 1  \n\"name\" -\u003e \"cruii\"\n\"param2\" -\u003e \"cruii\"  \n2 -\u003e \"1\"  \n\"param3\" -\u003e \"1\"\n```\n所以可以在SQL映射文件中如下两种方式配置都可以获取到参数的值\n\n第一种方式:\n\n```xml\n\u003cselect id=\"findByIdAndNameWithGender\" resultType=\"employee\"\u003e\n    SELECT\n        *\n    FROM\n        tb_employee\n    WHERE\n        emp_id = #{empId}\n    AND\n        emp_name = #{empName}\n    AND\n        emp_gender = #{param3}\n\u003c/select\u003e\n```\n第二种方式:\n\n```xml\n\u003cselect id=\"findByIdAndNameWithGender\" resultType=\"employee\"\u003e\n    SELECT\n        *\n    FROM\n        tb_employee\n    WHERE\n        emp_id = #{param1}\n    AND\n        emp_name = #{param2}\n    AND\n        emp_gender = #{param3}\n\u003c/select\u003e\n```\n至此,Mybatis的参数处理过程源码分析完毕.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmozhu811%2Flearning-mybatis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmozhu811%2Flearning-mybatis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmozhu811%2Flearning-mybatis/lists"}