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语句进行融合了。
本篇篇幅有点长,后期拆分一下。
县长喊话回家过年的湖南桑植年味浓!春节旅游总收入大增去年底,喊出有钱无钱回家过年的湖南省张家界市桑植县委副书记县长梁高武获得众多网友点赞,桑植县也因此受到关注,被网友评价为有温情的家乡。对歌打糍粑看村晚南都记者注意到,今年的桑植县年
三亚的天价旅游深层次的问题是什么?通过近期各种关于三亚天价旅游的新闻看,吃瓜群众有了几种不同的看法,要么是事不关己,谁有钱谁去被宰,一个愿打一个愿挨,要么是义愤填膺,有关部门为什么不进行合理性的规范,我也在思考几个
西南大学女博士举报遭导师潜规则西南大学女博士举报遭导师潜规则校方通报赵某与一博士生存在不正当关系。取消研究生导师资格,已报请主管部门批准撤销教师资格。众所周知,在家里你看见一只蟑螂时,地板下,沙发底已经有一大堆
科技强国在路上丨春节期间这些技术创新项目不停步系列报道科技强国在路上,今天关注面向国家重大需求的技术创新项目。01hr重器再添中国心8米直径盾构机主轴承将应用从穿山越岭到过江跨海,盾构机是国民经济和建设的重大装备。作为盾构机心
对话大家丨李稻葵2023年是中国经济和金融的大年央广网北京1月26日消息(记者门庭婷)把握乘势而上的现在,迎接触手可及的未来。央广网特别策划对话大家栏目今天邀请到了清华大学中国经济思想与实践研究院院长李稻葵教授,与广大网友分享中
守护城市环境鲁西新区假期不打烊中国山东网感知山东1月26日讯(记者吴鲁刚通讯员王宾)百节年为首,在这个欢乐祥和的新春佳节,鲁西新区综合行政执法局园林管理办公室周密部署,精心组织,加大工作力度,全力维护市容市貌,
2023年河北省重点项目公布!涉地铁医院高速高校日前,经省政府批准,省发展改革委印发河北省2023年省重点建设项目名单。2023年省重点建设项目共安排507个,总投资1。32万亿元,年度预计投资2600。1亿元。其中,新开工项目
大展宏兔兴银理财首席研究官徐寒飞经济重启复苏之年,迎接更多投资机会天地风霜尽,乾坤气象和。虎年轻舟已过,曾经多少艰难奋勇兔年可望春山,此间多少期许祝愿。挥别金虎,迎来玉兔。在这辞旧迎新的时刻,中国证券报邀请各大金融机构代表,为您送上新春祝福,分享
接续奋斗,推动经济运行整体好转中国经济韧性强潜力大活力足,长期向好的基本面依然不变贯彻五大政策,落实六个统筹,形成共促高质量发展合力,我们一定能为全面建设社会主义现代化国家开好局起好步数字记录成就,奋斗刻画历史
汇聚经济发展的澎湃动能和强劲信心田间地头,种粮户精心管护农田工厂车间,一线工人铆足干劲生产物流运输线上,工作人员加班加点保通畅浓浓年味里,许许多多劳动者干劲十足,用辛勤和汗水铸就新春最美的风景线,汇聚经济发展的澎
2。51亿人次出游!旅游热碰上传播力超强病毒,有效防控该怎么做?春节假期已经开始一段时间,此前不少人出游计划都被疫情等因素打断,此次假期国家开放管控,他们选择旅游过年放松心情,因此各地人流量也一直处于增加趋势。然而长途出行可能带来的病毒感染,让
国乒劲敌34岁生日!4届奥运斩获6枚奖牌,世界杯力压马龙夺首冠中国乒乓球男队最大的对手就是东京奥运会男团亚军德国队,这支队伍拥有2位世界冠军成员,分别是波尔和奥恰洛夫,其中后者迎来了34岁生日,他目前除了国乒主力之外男线劲敌获得奥运会奖牌最多
施法!博格巴请巫做法事件如果严重或将影响世界杯出场前几日,博格巴的亲二哥亲自下来手撕博格巴的一些不为人知的秘密,其中提到了博格巴请巫师做法给法甲球星姆巴佩施咒,这一消息引起了姆巴佩和其团队的注意,并且法国国家队方面也听到了风声。据
迎关键考察期!英超的巴西国脚们为进世界杯名单抢破头记者寒冰报道英超的巴西球员虽然创造了历史纪录,但毕竟安东尼卡塞米罗帕奎塔洛迪是刚抵达英超,能否在世界杯前的两个月时间打出状态站稳脚跟还是未知数。上半年成为纽卡斯尔联核心的吉马良斯尚
姆巴佩我们这支球队有两名个性很强的球员,但我对内马尔很尊重直播吧9月6日讯北京时间周三凌晨的欧冠小组赛第一轮比赛将上演一场焦点战,到时巴黎圣日耳曼会坐镇王子公园球场迎战尤文。在本场比赛之前的新闻发布会上,巴黎圣日耳曼前锋姆巴佩谈论了自己和
重庆小面制作配方各种调味料的调制炒制芽菜或者酸菜配料芽菜500G味精10G色拉油50G,制作方法1。冷锅热油2油烧热以后放入芽菜,把芽菜的水分炒干3水分炒干后,放入味精4翻炒均匀后关火,备用。制作
超市这3款白酒,可以放心囤,全是100纯酿,不含1滴香精香料超市这3款白酒,可以放心囤,全是100纯酿,不含1滴香精香料香精勾兑酒到底有多猖狂?对此,曾有老酒友这样告诉我10瓶酒中最少有6瓶是勾兑的!至于事实真相到底如何,我不知道,但勾兑酒
罗浮山游记一两天8月25日,食粮填满饥饿后便正式开启了温泉之行。天公作美,一路上清风破窗而入,让路途缩短了近一半。为了充分享受大自然馈赠的纯天然,五万多米的路程也被开出了五万八千里的感觉,悠哉乐哉
连接时空的中国古桥金龙桥,云南丽江,2021年5月赵亚洲金龙桥始建于清代光绪二年(1876年),光绪六年(1880年)建成,是我国现存桥面最宽铁索最多的铁链桥,曾有万里长江只一桥的称喻,是川滇藏三省
娱眼5000万人次收看站姐反水直播,粉丝终于清醒了你听说过林彦俊丁泽仁吗?没听说过也没关系。这两位在2018偶像练习生被粉丝所认识的艺人,出道以来热度最高的话题,不是什么事业巅峰代表作,而是被自己的粉丝集体反水送上了热搜。脱粉直播
欧拉好猫夏日出行的伴侣什么才是打开夏天的方式?吃西瓜撸串,还是夜跑我觉得这一切都没有一场说走就走的旅行来得畅快。但是对于平日里在都市奋斗的新时代女性来讲,长途自驾有点不太现实,周末来一场郊外短途自驾却是
来了非洲,我竟然迷上了逛超市本人在国内,很少逛商场超市,需要买啥时,都是直接到所在柜台,快速买完走人,从没感受到美女们在商场超市流连忘返的乐趣。可是,来非洲不久,逛超市就成为我的生活必需品。布拉柴虽然是首都,