mybatis中动态sql执行原理

mybatis中动态sql执行原理,第1张

解释器模式: 初始化过程中构建出抽象语法树,请求处理时根据参数对象解释语法树,生成sql语句

工厂模式: 为动态标签的处理方式创建工厂类(SqlTagHandlerFactory),根据标签名称获取对应的处理方式。

策略模式: 将动态标签处理方式抽象为接口,针对不同标签有相应的实现类。解释抽象语法树时,定义统一的解释流程,再调用标签对应的处理方式完成解释中的各个子环节

mybatis的源码中查看生成的sql语句,参考执行以下代码即可。具体代码如下:

把里面PooledDataSource类的log输出部分,换成logwarn之后,重新打jar包,放到项目中,日志级别改为info,如:

log4jrootLogger=info, stdout, R  

  

log4jappenderstdout=orgapachelog4jConsoleAppender  

log4jappenderstdoutlayout=orgapachelog4jPatternLayout  

log4jappenderstdoutlayoutConversionPattern=[service] %d - %c -%-4r [%t] %-5p %c %x - %m%n  

  

log4jappenderR=orgapachelog4jDailyRollingFileAppender  

log4jappenderRFile=/logs/servicelog  

log4jappenderRlayout=orgapachelog4jPatternLayout  

log4jappenderRlayoutConversionPattern=[service] %d - %c -%-4r [%t] %-5p %c %x - %m%n  

  

log4jloggercomibatis = debug  

log4jloggercomibatiscommonjdbcSimpleDataSource = debug  

log4jloggercomibatiscommonjdbcScriptRunner = debug  

log4jloggercomibatissqlmapengineimplSqlMapClientDelegate = debug  

log4jloggerjavasqlConnection = debug  

log4jloggerjavasqlStatement = debug  

log4jloggerjavasqlPreparedStatement = debug  

log4jloggerjavasqlResultSet =debug

<!-- 传递多参数的array参数表的foreach使用 -->

<select id="getStudentAllArray" resultMap="reusltMap_student"

resultType="comdepponmybatisexsiceTestStudent"

parameterType="array">

select from t_maydel_student where stunum in

<foreach collection="array" index="index" item="item" open="(" separator="," close=")">

#{item}

</foreach>

</select>

从命名上可以看出,这个是一个 Builder 模式的,用于创建 SqlSessionFactory 的类。SqlSessionFactoryBuilder 根据配置来构造 SqlSessionFactory。其中配置方式有两种:

mybatis-configxml 就是我们的配置文件:

Java Config 相比较 XML 文件的方式而言,会有一些限制。比如修改了配置文件需要重新编译,注解方式没有 XML 配置项多等。所以,业界大多数情况下是选择 XML 文件的方式。但到底选择哪种方式,这个要取决与自己团队的需要。比如,项目的 SQL 语句不复杂,也不需要一些高级的 SQL 特性,那么 Java Config 则会更加简洁一点;反之,则可以选择 XML 文件的方式。

创建配置文件解析器XMLConfigBuilder

解析mybatis-configxml里的配置为Configuration对象,Mybatis的全局配置对象。

XMLConfigBuilder#parseConfiguration解析mapper下的xml

XMLMapperBuilder#bindMapperForNamespace,根据xml里的 namespace 反射出 mapper接口 的 class,如果有mapper接口,则把该mapper接口的class添加到Configuration的mapperRegistry里。

如果该接口已经注册,则抛出已经绑定的异常。

为该接口注册MapperProxyFactory,但这里只是注册其创建MapperProxy的工厂,并不是创建MapperProxy。

如果Mapper对应的xml资源未加载,触发xml的绑定 *** 作,将xml中的sql语句与Mapper建立关系。

addMapper方法,只是为Mapper创建对应对应的MapperProxyFactory。

根据Mapper接口与SqlSession创建MapperProxy对象。

根据接口类获取MapperProxyFactory。

调用MapperProxyFactory的newInstance创建MapperProxy对象。

SqlSessionFactory 顾名思义,是用于生产 SqlSession 的工厂。 通过如下的方式来获取 SqlSession 实例:

SqlSession 包含了执行 SQL 的所有的方法。以下是示例:

当然,下面的方式可以做到类型安全:

MapperProxy是MapperProxyFactory使用SqlSession创建出来的。所以MapperProxy中包含SqlSession。

可以看到MapperProxy调用invoke方法,进而调用MapperMethod的execute(),这些MapperMethod就是和你要执行的命令相关,比如执行select语句,则会通过SqlSession的select()方法,最终调用到Executor的query方法。Executor会再协调另外三个核心组件。

MapperProxy:

MapperMethod:

插件的构建:

谈原理首先要知道StatementHandler,ParameterHandler,Result Handler都是代理,他们是Configuration创建,在创建过程中会调用interceptorChainpluginAll()方法,为四大组件组装插件(再底层是通过Pluginwrap(target,XX, new Plugin( interceptor))来来创建的)。

插件链是何时构建的:

在执行SqlSession的query或者update方法时,SqlSession会通过Configuration创建Executor代理,在创建过程中就调用interceptor的pluginAll方法组装插件。然后executor在调用doQuery()方法的时候,也会调用Configuration的newStatementHandler方法创建StatemenHandler(和上面描述的一样,这个handler就是个代理,也是通过interceptorChain的pluginAll方法构建插件)

插件如何执行:

以statementhandler的prepare方法的插件为例,正如前面所说,statementhandler是一个proxy,执行他的prepare方法,将调用invokeHandler的invoke方法,而invokeHandler就是Pluginwrap(target, xxx, new Plugin(interceptor))中的第三个参数,所以很自然invokeHanlder的invoke的方法最终就会调用interceptor对象的intercept方法。

Mybatis的插件配置在configuration内部,初始化时,会读取这些插件,保存于Configuration对象的InterceptorChain中。

orgapacheibatispluginInterceptorChainjava源码。

上面的for循环代表了只要是插件,都会以责任链的方式逐一执行,所谓插件,其实就类似于拦截器。

插件的编写

插件必须实现orgapacheibatispluginInterceptor接口。

-intercept()方法:执行拦截内容的地方,拦截目标对象的目标方法的执行

-plugin()方法:决定是否触发intercept()方法。 作用:包装目标对象,包装就是为目标对象创建一个代理对象

-setProperties()方法:给自定义的拦截器传递xml配置的属性参数。将插件注册时的property属性设置进来

下面自定义一个拦截器:

为什么要写Annotation注解?注解都是什么含义?

Mybatis规定插件必须编写Annotation注解,是必须,而不是可选。@Intercepts注解:装载一个@Signature列表,一个@Signature其实就是一个需要拦截的方法封装。那么,一个拦截器要拦截多个方法,自然就是一个@Signature列表。

type = Executorclass, method = "query", args = { MappedStatementclass, Objectclass, RowBoundsclass, ResultHandlerclass }

解释:要拦截Executor接口内的query()方法,参数类型为args列表。

Pluginwrap(target, this)是干什么的?

使用JDK的动态代理,给target对象创建一个delegate代理对象,以此来实现方法拦截和增强功能,它会回调intercept()方法。

Mybatis可以拦截哪些接口对象?

Mybatis只能拦截ParameterHandler、ResultSetHandler、StatementHandler、Executor共4个接口对象内的方法。

重新审视interceptorChainpluginAll()方法:该方法在创建上述4个接口对象时调用,其含义为给这些接口对象注册拦截器功能,注意是注册,而不是执行拦截。

拦截器执行时机:plugin()方法注册拦截器后,那么,在执行上述4个接口对象内的具体方法时,就会自动触发拦截器的执行,也就是插件的执行。

Invocation

可以通过invocation来获取拦截的目标方法,以及执行目标方法。

分页插件原理

由于Mybatis采用的是逻辑分页,而非物理分页,那么,市场上就出现了可以实现物理分页的Mybatis的分页插件。 要实现物理分页,就需要对String sql进行拦截并增强,Mybatis通过BoundSql对象存储String sql,而BoundSql则由StatementHandler对象获取。

因此,就需要编写一个针对StatementHandler的query方法拦截器,然后获取到sql,对sql进行重写增强。

最简单的直接设置resultType=“map”,得到的就是一个List<Map<String,Object>>

每一个Map的key就是查询结果的列名,也就是表的字段名。当然sql语句中最好as一下

复杂一点可以自定义一个resultMap标签

<resultMap id="result" type="javautilMap">

<result property="c1" column="column1"/>

<result property="c2" column="column2"/>

</resultMap>

其中result可以自定义,select标签中的resultMap="result",和上面的resultMap标签的id对应

property设置的是自定义的字段名称,也就是结果集中每一个Map的key

column设置查询结果的字段名称

然后通过

for(List<Map<String,Object> map:list){

mapget("c1");//获取c1对应的字段的值

。。。

}

oracle 10g的DBMS_XPLAN包中display_cursor函数不同于display函数,display_cursor用于显示SQL语句的真实的执行计划,在大多数情况下,

显示真实的执行计划有助于更好的分析SQL语句的全过程,尤其是运行此SQL语句实时的I/O开销。通过对比预估的I/O与真实的I/O开销来判断

SQL语句所存在问题,如缺少统计信息,SQL语句执行的次数,根据实际中间结果集的大小来选择合适的连接方式等。本文仅仅讲述

display_cursor函数的使用。

一、display_cursor函数用法

1、display_cursor函数语法

DBMS_XPLANDISPLAY_CURSOR(

sql_id IN VARCHAR2 DEFAULT NULL,

cursor_child_no IN NUMBER DEFAULT NULL,

format IN VARCHAR2 DEFAULT 'TYPICAL');

2、display_cursor函数参数描述

sql_id

指定位于库缓存执行计划中SQL语句的父游标。默认值为null。当使用默认值时当前会话的最后一条SQL语句的执行计划将被返回

可以通过查询V$SQL 或V$SQLAREA的SQL_ID列来获得SQL语句的SQL_ID。

cursor_child_no

指定父游标下子游标的序号。即指定被返回执行计划的SQL语句的子游标。默认值为0。如果为null,则sql_id所指父游标下所有子游标

的执行计划都将被返回。

format

控制SQL语句执行计划的输出部分,即哪些可以显示哪些不显示。使用与display函数的format参数与修饰符在这里同样适用。

除此之外当在开启statistics_level=all时或使用gather_plan_statistics提示可以获得执行计划中实时的统计信息

有关详细的format格式描述请参考:dbms_xplan之display函数的使用 中format参数的描述

下面给出启用统计信息时format新增的修饰符

iostats 控制I/O统计的显示

last 默认,显示所有执行计算过的统计。如果指定该值,则只显示最后一次执行的统计信息

memstats 控制pga相关统计的显示

allstats 此为iostats memstats的快捷方式,即allstats包含了iostats和memstats

run_stats_last 等同于iostats last。只能用于oracle 10g R1

run_stats_tot 等同于iostats。只能用于oracle 10g R1

抓一个最近一小时最消耗IO的SQL:

SELECT sql_id, COUNT()

FROM gv$active_session_history ash, gv$event_name evt

WHERE ashsample_time > SYSDATE - 1 / 24

AND ashsession_state = 'WAITING'

AND ashevent_id = evtevent_id

AND evtwait_class = 'User I/O'

GROUP BY sql_id

ORDER BY COUNT() DESC;

执行上面的SQL:

SQL> SELECT sql_id, COUNT()

FROM gv$active_session_history ash, gv$event_name evt

2 3 WHERE ashsample_time > SYSDATE - 1 / 24

4 AND ashsession_state = 'WAITING'

5 AND ashevent_id = evtevent_id

6 AND evtwait_class = 'User I/O'

7 GROUP BY sql_id

8 ORDER BY COUNT() DESC;

SQL_ID COUNT()

------------- ----------

g7fu6qba82m6b 668

63r47zyphdk06 526

9f5m4wd88nc1h 514

593p47drw5fhk 232

br91w16jzy4fu 120

4fvwyjpnh6tp7 78

gm0nrbfuj8kzr 70

2184k363hw4xd 68

gc4dajs7g5myy 46

8vrk9sfuwfdgq 42

ccpnb4dwdmq21 40

查看SQL的执行计划:

SELECT FROM TABLE(dbms_xplandisplay_cursor('g7fu6qba82m6b'));

在SQLPLUS中执行:

SQL> set pagesize 2000

SQL> SELECT FROM TABLE(dbms_xplandisplay_cursor('g7fu6qba82m6b'));

PLAN_TABLE_OUTPUT

------------------------------------------------------------------------------------------------------------------------

SQL_ID g7fu6qba82m6b, child number 0

-------------------------------------

UPDATE "CPDDS_PDATA""CDM_LEDGER" SET "CSTM_NAME" = :a1,"CSTM_NO" =

:a2,"PAPER_TYPE" = :a3,"PAPER_NO" = :a4,"CURR_TYPE" = :a5,"SVT_NO" =

:a6,"BAL_DIR" = :a7,"BAL" = :a8,"AVAL_BAL" = :a9,"NORM_FRATIO" =

:a10,"PK_BAL" = :a11,"DR_ACCU" = :a12,"CR_ACCU" = :a13,"LAST_TRAN_DATE" =

:a14,"LAST_TRAN_TIME" = :a15,"PRT_LINE_NUM" = :a16,"NOREG_PK_REC_NUM" =

:a17,"PK_NO" = :a18,"PWD" = :a19,"FLAG" = :a20,"FRZ_FLAG" =

:a21,"CARD_HOLD_FLAG" = :a22,"PK_HOLD_FLAG" = :a23,"BGN_INT_DATE" =

:a24,"OPEN_DATE" = :a25,"ACC_HOLD_FLAG" = :a26,"CLS_DATE" =

:a27,"OPEN_TLR" = :a28,"CLS_TLR" = :a29,"CLS_INT" = :a30,"OPEN_INST" =

:a31,"ADD_NUM" = :a32,"DAC" = :a33,"FRZ_TIMES1" = :a34,"FRZ_TIMES2" =

:a35,"HOST_SEQNO" = :a36,"D_UPDATE_DATE" = :a37 WHERE "ACC" = :b0

Plan hash value: 319441092

-----------------------------------------------------------------------------------

| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |

-----------------------------------------------------------------------------------

| 0 | UPDATE STATEMENT | | | | 3 (100)| |

| 1 | UPDATE | CDM_LEDGER | | | | |

| 2 | INDEX UNIQUE SCAN| I_CDM_LEDGER | 1 | 269 | 2 (0)| 00:00:01 |

-----------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

2 - access("ACC"=:B0)

29 rows selected

总结

1、与display函数不同,display_cursor显示的为真实的执行计划

2、对于format参数,使用与display函数的各个值,同样适用于display_cursor函数

3、当statistics_level为all或使用gather_plan_statistics提示可以获得执行时的统计信息

4、根据真实与预估的统计信息可以初步判断SQL效率低下的原因,如统计信息的准确性、主要的开销位于那些步骤等

mybatis的sql和你在数据库客户端执行的sql是一样的,但是在mybatis中调用的sql一般都是动态的,所以用到了参数传递。这个mybatis有对应的标签以及相应的变量来实现。你可以搜索下mybatis标签。同时给你一个参考的你看看,这个是一个查询用户的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

<select id="queryUsers" parameterType="map" resultType="xxxxxxbeanUserBean">

<![CDATA[

select

ID,

LOGIN_NAME AS loginName,

PASSWORD,

REAL_NAME AS realName,

POSITION,

(SELECT DPOSITION_NAME FROM UNIT_POSITION D WHERE DPOSITION_CODE=TPOSITION) POSITIONNAME,

USER_TYPE AS userType,

SEX,

PID,

TO_CHAR(TBIRTHDAY,'YYYY-MM-DD') BIRTHDAY,

EMAIL,

CONTACT_TEL AS contactTel,

CONTACT_MOBILE AS contactMobile,

CONTACT_FAX AS contactFax,

CONTACT_ZIP AS contactZip,

CONTACT_ADDR AS contactAddr,

STATUS,

EDUCATION,

(SELECT DEDUCATION_NAME FROM UNIT_EDUCATION D WHERE DEDUCATION_CODE=TEDUCATION AND DSTATUS=0) EDUCATIONNAME,

NATION,

POLITICAL,

REMARK,

TO_CHAR(TCREATE_DATE,'YYYY-MM-DD HH24:MI:SS') createDate,

(SELECT DREAL_NAME FROM UNIT_USER D WHERE DID= TCREATE_USER_ID) createUserId,

TO_CHAR(TUPDATE_DATE,'YYYY-MM-DD HH24:MI:SS') updateDate,

(SELECT DREAL_NAME FROM UNIT_USER D WHERE DID= TUPDATE_USER_ID) updateUserId

from UNIT_USER T

]]>

<where>

TSTATUS='1'

<if test="realName !=null and realName !=''">

and TREAL_NAME like '%${realName}%'

</if>

<if test="nexusDpartment !=null">

AND TID IN (SELECT DISTINCT DUSER_ID FROM UNIT_USER_DEPT D WHERE DDEPT_CODE IN (${nexusDpartment}))

</if>

<if test="deptCode !=null and deptCode !=''">

AND TID IN (SELECT DISTINCT DUSER_ID FROM UNIT_USER_DEPT D WHERE DDEPT_CODE = #{deptCode})

</if>

</where>

<if test="sort != null and sort != ''">

order by ${sort}

<if test="direction != null and direction != ''">

${direction}

</if>

</if>

</select>

在我的springMVC和mybatis整合中设置了如下内容

<!-- 配置mapper接口 -->

<bean class="orgmybatisspringmapperMapperScannerConfigurer">

<property name="basePackage" value="dao" />

</bean>

<bean id="sqlSession" class="orgmybatisspringSqlSessionTemplate">

<constructor-arg index="0" ref="sqlSessionFactory" />

<constructor-arg index="1" value="BATCH" />

</bean>

<!-- 事务配置 -->

<bean id="transactionManager"

class="orgspringframeworkjdbcdatasourceDataSourceTransactionManager">

<property name="dataSource" ref="dataSource" />

</bean>

就是因为上面设置了 <constructor-arg index="1" value="BATCH" /> 这句引起的。去掉即可。

由于框架是别人搭建的,不知道此处的设置是何用意。

<constructor-arg index="1" value="BATCH" />这是设置如此是想要进行批量 *** 作,但是经测试没有此处的设置也可进行批量 *** 作。大胆果断的删除即可。

以上就是关于mybatis中动态sql执行原理全部的内容,包括:mybatis中动态sql执行原理、如何在mybatis中调试查看生成的sql语句、mybatis的sql文件怎么通过下标获取对象里数组类型的字段的某个值等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/10178762.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-06
下一篇 2023-05-06

发表评论

登录后才能评论

评论列表(0条)

保存