MyBatis执行SQL的原理
在开发中使用mybatis,是将Mapper接口生成的代理类存入到spring IOC容器中。然后,通过注解获取代理对象,调用对应接口方法,间接调用到 mapper xml中的SQL。
如果想了解mybatis是如何通过接口实现SQL操作可以看一下这个:https://www.toutiao.com/article/7206541912168268343
一般接口都会有入参,和返回对象。那么mybatis是如何进行封装的呢?
mapper xml 文件中的语句 经常会使用一些标签 if 、foreach、set 、where 等,是如何实现的呢?
Mybatis 参数的解析
参数解析这一步骤,通常是在执行SQL语句之前就要完成的,因此,需要查看SQL执行之前的代码。问题是从哪里开始查看源码呢?既然不知道具体是哪里进行了参数解析,那就从 调用代理对象开始 。
代理对象调用方法执行 MapperProxy.invoke 方法,MapperProxy.invoke方法中调用 MapperProxy.cachedInvoker 方法,并且该方法中创建一个 MapperMethod 实例对象。
MapperMethod对象中有两个成员常量 ,SqlCommand 和 MethodSignature对象。后面会用到。
最终MapperProxy.cachedInvoker 方法返回一个 PlainMethodInvoker 对象,接着PlainMethodInvoker对象调用自己的 invoke 方法(注意 :这就是一个invoke方法,跟代理没关系,不要被迷惑了)
调用方法后,执行 MapperMethod对象的 execute 方法。该方法中
method.convertArgsToSqlCommandParam(args),就是我们要找的参数解析方法。
这块代码可以不看。public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: 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 if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); 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; }
convertArgsToSqlCommandParam 方法,MethodSignature 对象是创建MapperMethod时一个成员常量。
ParamNameResolver 是 MethodSignature中的一个成员常量。
ParamNameResolver 对象中 ParamNameResolver 方法。该方法获取 带有@param注解的参数。
ParamNameResolver是用来解析 @param 类型的public ParamNameResolver(Configuration config, Method method) { //默认使用 实际参数名 this.useActualParamName = config.isUseActualParamName(); final Class<?>[] paramTypes = method.getParameterTypes(); final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap map = new TreeMap<>(); int paramCount = paramAnnotations.length; // get names from @Param annotations 获取@Param注解的参数 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 (useActualParamName) { 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); }
用来解析实体参数 (param1, param2, ...)public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; } //如果只有一个参数,并且没有使用@Param注解,就直接返回第一个参数 else if (!hasParamAnnotation && paramCount == 1) { Object value = args[names.firstKey()]; return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); } //有多个参数,则封装成一个map,key为参数参数名称, //参数使用@Param注解,名称就是注解中的值,否则key为arg0、arg1这种类型, //同时,一定含有key为param1、param2的参数,值就是传入的值 else { final Map param = new ParamMap<>(); int i = 0; for (Map.Entry entry : names.entrySet()) { //没有@Param注解,key为arg0、arg1这种类型 param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) final String genericParamName = GENERIC_NAME_PREFIX + (i + 1); // ensure not to overwrite parameter named with @Param if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
小结:
ParamNameResolver 对象解析参数,两种方法:
ParamNameResolver方法:解析形参,判断是否使用了@Param注解。
getNamedParams方法:封装实参,如果只有一个,并且没有使用@Param注解,就直接返回第一个参数值,否则封装成map。 多个参数,key为参数参数名称,使用@Param注解,key值就是注解中的值,否则key为arg0、arg1这种类型,并且,一定含有key为param1、param2…的参数,作为值传入。
上面的这一部分主要是将参数解析。
Mybatis 解析好的参与SQL整合
参数解析完成后,执行SqlSession中的方法。
以selectList方法为例,首先获取MappedStatement对象,给对象包含SQL。
wrapCollection(parameter) 方法对解析后的参数进行了再次封装。
对集合、数组、列表进行了二次封装,其他类型原样返回。
执行 executor.query(ms, wrapCollection(parameter), rowBounds, handler) 代码后,进入BaseExecutor类中query方法。
BoundSql boundSql = ms.getBoundSql(parameter);
通过 mappedStatement 对象,获取 BoundSql对象。
然后,根据mappedStatement 成员变量 sqlSource 调用 getBoundSql 。BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
BoundSql语句的解析主要是通过对 #{} 字符的解析,将其替换成?。#{} 中的key属性以及相应的参数映射,比如javaType、jdbcType等信息均保存至BoundSql的parameterMappings属性中。BoundSql类中的成员:
ParameterObject :传入的参数。
一个参数对象时:为该参数的类型;
多个参数对象时:为ParamMap对象。
ParameterMapping:#{name}形式的引用变量,变量会在解析Mapper.xml文件中的语句时,就被替换成占位符"?"。ParameterMapping类记录 对应的变量信息 。
additionalParameters:使用Criteria对象的sql。
MetaObject:Mybatis在sql参数设置和结果集映射里经常使用到这个对象。
DynamicSqlSource 或者RowSqlSource,前者表示动态SQL ,后者表示静态SQL 。
动态SQL:行该sql相关操作的时候才根据传入的参数进行解析。(mybatis 标签 if 、where 等)
以StaticSqlSource为例:
getBoundSql直接返回BoundSql 对象。
代码返回到:BaseExecutor 类query方法中。
query 方法调用 queryFromDatabase 方法,queryFromDatabase 再调用
doQuery(ms, parameter, rowBounds, resultHandler, boundSql)方法。
doQuery 方法中 prepareStatement 就是给预编译SQL进行赋值的。@Override public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //预编译sql,并且给参数赋值 stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
prepareStatement方法中 handler.parameterize(stmt) 就是设置参数值。
调用PreparedStatementHandler 类的 parameterize方法。
parameterize方法中的 parameterHandler.setParameters((PreparedStatement) statement) 调用 DefaultParameterHandler setParameters 方法。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
千辛万苦终于是把解析的参数与SQL语句进行融合了。
本篇篇幅有点长,后期拆分一下。
荣耀Magic4Pro手机,更均衡的旗舰手机hello,大家好,本期我们要聊的是这款荣耀magic4pro手机。外观颜值,搭载了一块六点八一英寸的四曲面屏幕,左上角双打孔设计,分辨率两千八百四十八乘一千三百一十二,支持ltp
vivoX80Pro手机,聚焦没有短板哈喽,大家好,本期我们要聊的是这款VIVOX80Pro手机。方圆直径设计的镜头模组的辨识度很高,加上陶瓷和塑皮材质的背壳,尽显高端优雅。后置五千万主摄加4800万超广角加1800万
威马汽车节能环保省钱近年来,在科技不断进步政府不断出台相关扶持政策等有利因素的推动下,中国新能源市场迎来了快速发展。作为扎根于中国的硬科技创新代表,汽车始终坚持品牌理念,致力于将非凡的技术奉献给每一个
明明秋已至,我却想在银杏小镇的夏天停留转眼已至八月末暑假也剩下不多的时日清凉的气候清新的绿意清静的时光让我最钟意腾冲银杏小镇的夏天明明秋天已至万众期待的金黄童话即将来临而我却想再停留一会抓住夏天的尾巴带你逛一逛银杏小镇
本田摩托在国内即将9月15日发布的几款新机种,敬请期待最近本田摩托联手国内的五羊本田和新大洲本田同时官宣了在今年也就是2022年9月15日正式发布几款新机种,具体是哪些机种,我们只能大概估计,不妨大家一起来猜一猜。从五羊本田和新大洲本
特斯拉8月销量预估7。7万台,环比增长173近日,据全国乘用车市场信息联席会秘书长崔东树透露,8月特斯拉交付量预估达到7。7万左右,环比增长173。此前,6月份特斯拉产量曾创下纪录。乘联会数据显示,2022年6月份特斯拉中国
极飞科技上半年海外业务同比大增260加快智慧农业设备全球业务布局本报记者李昱丞见习记者王镜茹极飞科技披露2022年上半年海外业务成绩单。数据显示,今年上半年,极飞科技的海外业务与去年同期相比增长了260,其中,拉丁美洲与东南亚地区对极飞科技智能
卫星导航芯片华为VR!公司主打产品已用于北斗卫星导航基建神宇股份华为卫星导航VR芯片,公司生产的射频同轴电缆客户以中大型电子设备生产商为主,开发出的应用于5G消费终端的细微极细射频同轴电缆等产品已实现批量销售。客户所服务的终端电子产品包
当我们高喊支持国产时,苹果的真实情况又到了苹果发布新品的月份,苹果各地的经销商,已经开始大量招聘,为接下来的硬仗做准备!!!自从华为没有5G芯片之后,手机销量直线下滑,身边大部分人都叫嚣着我们要支持国产,但苹果的销量
物理学的哲学思考物理学的魅力在于它的普遍适用性,在于它带给我们的哲学思考。我们知道,物理学是探讨物质的结构和运动,探索大自然客观规律的学科。物理学的前身是自然哲学,著名的物理学家艾萨克牛顿就是在他
中兴Axon40Ultra旗舰手机,屏下摄像头和三摄我真的爱hello,大家好,本期我们要分享的是这款中兴Axon40Ultra手机。外观颜值,得益于全新一代柔性屏下摄像技术,屏幕完全没有开孔,实现了真全面屏的效果,屏幕尺寸提升为六点八英寸
成都某地房价一万七跌到六千?揭穿中介忽悠你高位接盘的五大谎言成都某地房价17000跌到6000是什么感觉?揭穿中介忽悠你去高位接盘的五大谎言。2020年,曾经有些远郊楼盘,甚至根本不是成都范围,比如属于眉山的视高,卖到了17000元。而如今
央行最新发声!提及硅谷银行中国人民银行副行长宣昌能18日在北京表示,当前全球经济金融环境的复杂变化给财富管理带来新的挑战,中国坚定推进高水平对外开放,为全球资产的多元化配置提供了新的机遇和选择。宣昌能表示,
前投行高管接受调查,昔日保荐王国信证券风光何时再现?作者李海霞编辑付影来源独角金融7年前,国信证券(002736。SZ)前保代被没收违法所得千万罚款千万终身市场禁入在市场掀起一场风波7年后,该保代被通报正在接受调查,再次引发市场关注
在协同发展大坐标中提速增效业界探索城市产业运营来源人民网原创稿龙河新城展厅实景图。来源受访方供图当前,京津冀协同发展正在向广度深度拓展。如何在协同大文章上找思路?有专家表示,推进区域一体化发展应聚焦承接非首都功能疏解推动协同化
电企数字化转型需有产业思维和长效机制在华为技术有限公司副总裁电力数字化军团首席执行官孙福友看来,电力行业是降碳的主力军,推动数字化技术融入新型电力系统建设并提供有效支撑,是电力能源信息等行业需共同面对的课题。在碳达峰
长春高新放量跌停,下跌80再创阶段新低,龙王变水货?3月财经新势力A股股市行情股市分析长春高新汤姆猫长春高新在股票价格下跌80。8的基础之上,放量跌停,一个交易日的成交量高达37亿元,股票价格再创阶段性的新低,曾经的行业龙头,曾经的
总投资26亿廊坊国际现代商贸物流CBD即将启动燕赵都市报纵览新闻记者李春炜河北省现代商贸物流基地建设又有新进展。3月21日,廊坊国际现代商贸物流CBD全球发布仪式将在廊坊国际现代商贸物流CBD(以下简称商贸物流CBD)召开。商
注意!这类银行卡将停用注意你手里的这种银行卡要被清理了!中国银行建设银行招商银行平安银行等逾30家商业银行陆续发布通知,将对长期睡眠的信用卡开展账户安全管理等工作。据去年7月中国银保监会发布关于进一步促
7天股价暴跌超70!超千亿元蒸发!这一银行,拟这样自救受美国硅谷银行倒闭影响,美国银行业持续承压,另一家地区性银行第一共和银行也陷入危机,很多储户担心第一共和银行的经营状况,纷纷从该银行提取存款。据美国纽约时报网站17日报道,美国第一
河北最南的点在哪里?大家都知道河北最南端在邯郸,但你知道邯郸最南端在哪里吗?邯郸最南端的县是魏县。这里南边跟河南省的安阳县内黄县清丰县南乐县接连,有一百多万人口,也算是平原上的一个人口大县。不过,虽然
河北蔚县三线探访,山沟里的迫击炮厂,人去楼空无限苍凉河北地处华北平原北部,东临渤海,西依太行山,北跨燕山,内环京津,从战略地位讲,既位于战争的前沿地带,又担负着京津外围防御的重任,在全国三线建设中的地位不容忽视。三线时期,一批具有神