背景:调试模式下,单步运行一个查询订单协议操作,记录了ibatis框架的执行动作,侧面剖析其原理。

一、简介:

1. dal 层的dao接口实现类通常会继承SqlMapClientDaoSupport。spring容器在初始化一个dao bean实例时,通常会注入两块信息DataSource(数据源)和sqlMapClient(主要是sql语句),这两块信息会封装到SqlMapClientTemplate

2. 其中数据源的实例通常采用apache的开源项目dbcp

代码配置如下:

  1. <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  2. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  3. <property name="url" value="xxxx" />
  4. <property name="username"><value>xxxx</value></property>
  5. <property name="password"><value>xxxxx</value></property>
  6. <property name="maxActive"><value>20</value></property>
  7. <property name="initialSize"><value>1</value></property>
  8. <property name="maxWait"><value>60000</value></property>
  9. <property name="maxIdle"><value>20</value></property>
  10. <property name="minIdle"><value>3</value></property>
  11. <property name="removeAbandoned"><value>true</value></property>
  12. <property name="removeAbandonedTimeout"><value>180</value></property>
  13. <property name="connectionProperties"><value>clientEncoding=GBK</value></property>
  14. </bean>

各配置参数的具体含义可参照:dbcp基本配置和重连配置

3. sqlMapClient

  1. <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  2. <property name="configLocation">
  3. <value>classpath:sqlmap.xml</value>
  4. </property>
  5. </bean>
  6. <bean id="sqlMapClientTddl" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
  7. <property name="dataSource" ref="tGroupDataSource" />
  8. <property name="configLocation" value="classpath:sqlmap.xml"/>
  9. </bean>

sqlMapClient,主要是借助于实现FactoryBean和InitializingBean两个接口,加载sql.xml文件资源信息,得到sqlMapClient组件

注:上面的sqlMapClient默认不配置数据源,后面的SqlMapClientTemplate优先从全局变量中取,如果没有再从sqlMapClient中查找。

  1. public DataSource getDataSource() {
  2. DataSource ds = super.getDataSource();
  3. return (ds != null ? ds : this.sqlMapClient.getDataSource());
  4. }

构造sqlMapClient组件的代码块。

  1. public void afterPropertiesSet() throws Exception {
  2. if (this.lobHandler != null) {
  3. // Make given LobHandler available for SqlMapClient configuration.
  4. // Do early because because mapping resource might refer to custom types.
  5. configTimeLobHandlerHolder.set(this.lobHandler);
  6. }
  7. try {
  8. this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties);
  9. // Tell the SqlMapClient to use the given DataSource, if any.
  10. if (this.dataSource != null) {
  11. TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance();
  12. DataSource dataSourceToUse = this.dataSource;
  13. if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) {
  14. dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource);
  15. }
  16. transactionConfig.setDataSource(dataSourceToUse);
  17. transactionConfig.initialize(this.transactionConfigProperties);
  18. applyTransactionConfig(this.sqlMapClient, transactionConfig);
  19. }
  20. }
  21. finally {
  22. if (this.lobHandler != null) {
  23. // Reset LobHandler holder.
  24. configTimeLobHandlerHolder.set(null);
  25. }
  26. }
  27. }

4. SqlMapExecutor

该接口是对SQL操作行为的抽象,提供了SQL单条执行和批处理涉及的所有操作方法

5. SqlMapTransactionManager 

该接口是对事务行为的抽象,提供了事务执行过程中涉及的所有方法。 

 

6. SqlMapClient 

该接口定位是SQL执行客户端,是线程安全的,用于处理多个线程的sql执行。它继承了上面两个接口,这意味着该接口具有SQL执行和事务处理的能力,该接口的核心实现类是SqlMapClientImpl。 



7. SqlMapSession 

该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程sql执行过程的session信息。该接口的核心实现类是SqlMapSessionImpl

8. MappedStatement 

该接口定位是单条SQL执行时的上下文环境信息,如SQL标识、SQL、参数信息、返回结果、操作行为等。 



9. ParameterMap/ResultMap 

该接口用于在SQL执行的前后提供参数准备和执行结果集的处理。

整体类图:

二、具体调用

接下来就到了数据持久层的代码调用,所有的数据库DML操作(增、删、改、查)都是借助于SqlMapClientTemplate来实现的。

  1. public OrderEnsureProtocolDO getOrderEnsureProtocolByOrderId(Long orderId) {
  2. if (orderId == null) {
  3. return null;
  4. }
  5. return (OrderEnsureProtocolDO) this.getSqlMapClientTemplate().queryForObject("MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID",
  6. orderId);
  7. }

如果一次执行的sql较多,我们会采用批处理的形式

  1. public void batchDeleteOfferSaleRecord(final List<Long> orderEntryIds) throws Exception {
  2. if (orderEntryIds == null || orderEntryIds.size() < 1 || orderEntryIds.size() > 50) {
  3. return;
  4. }
  5. this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
  6. @Override
  7. public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
  8. executor.startBatch();
  9. for (Long entryId : orderEntryIds) {
  10. executor.insert(MS_DELETE_SALE_RECORD, entryId);
  11. }
  12. return executor.executeBatch();
  13. }
  14. });
  15. }

不管采用上面哪种方式,查看源代码会发现,最后都是在调用execute(SqlMapClientCallback action)方法

  1. public Object queryForObject(final String statementName, final Object parameterObject)
  2. throws DataAccessException {
  3. return execute(new SqlMapClientCallback() {
  4. public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
  5. return executor.queryForObject(statementName, parameterObject);
  6. }
  7. });
  8. }
  1. public Object execute(SqlMapClientCallback action) throws DataAccessException {
  2. Assert.notNull(action, "Callback object must not be null");
  3. Assert.notNull(this.sqlMapClient, "No SqlMapClient specified");
  4. //获取session信息(SqlMapSessionImpl实例)
  5. SqlMapSession session = this.sqlMapClient.openSession();
  6. if (logger.isDebugEnabled()) {
  7. logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation");
  8. }
  9. Connection ibatisCon = null;
  10. try {
  11. Connection springCon = null;//数据库连接
  12. DataSource dataSource = getDataSource();
  13. boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy);
  14. try {
  15. //获取数据获连接
  16. ibatisCon = session.getCurrentConnection();
  17. if (ibatisCon == null) {
  18. springCon = (transactionAware ?
  19. dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource));
  20. //将数据源set到session会话中
  21. session.setUserConnection(springCon);
  22. if (logger.isDebugEnabled()) {
  23. logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation");
  24. }
  25. }
  26. else {
  27. if (logger.isDebugEnabled()) {
  28. logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation");
  29. }
  30. }
  31. }
  32. catch (SQLException ex) {
  33. throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
  34. }
  35. try {
  36. //执行SQL
  37. return action.doInSqlMapClient(session);
  38. }
  39. catch (SQLException ex) {
  40. throw getExceptionTranslator().translate("SqlMapClient operation", null, ex);
  41. }
  42. finally {
  43. 省略。。。一系列的关闭工作
  44. }
  45. }

SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理(里面注入了很多功能对象,负责具体的sql执行)

  1. public Object queryForObject(String id, Object paramObject) throws SQLException {
  2. return delegate.queryForObject(session, id, paramObject);
  3. }

经过N层重载,最后调用内部的通用方法

  1. 入参:
  2. id=MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID
  3. paramObject=26749329
  4. public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject) throws SQLException {
  5. Object object = null;
  6. //MappedStatement对象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件构建而成
  7. //调试中的ms为SelectStatement,具体的执行器
  8. MappedStatement ms = getMappedStatement(id);
  9. // 用于事务执行
  10. Transaction trans = getTransaction(session);
  11. boolean autoStart = trans == null;
  12. try {
  13. trans = autoStartTransaction(session, autoStart, trans);
  14. // 从RequestScope池中获取该次sql执行中的上下文环境RequestScope
  15. RequestScope request = popRequest(session, ms);
  16. try {
  17. // 执行sql
  18. object = ms.executeQueryForObject(request, trans, paramObject, resultObject);
  19. } finally {
  20. pushRequest(request);  //归还RequestScope
  21. }
  22. autoCommitTransaction(session, autoStart);
  23. } finally {
  24. autoEndTransaction(session, autoStart);
  25. }
  26. return object;
  27. }

接下来由MappedStatement.executeQueryForObject()来执行

  1. public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject)
  2. throws SQLException {
  3. try {
  4. Object object = null;
  5. DefaultRowHandler rowHandler = new DefaultRowHandler();
  6. //执行sql语句
  7. executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS);
  8. //结果处理,返回结果
  9. List list = rowHandler.getList();
  10. if (list.size() > 1) {
  11. throw new SQLException("Error: executeQueryForObject returned too many results.");
  12. } else if (list.size() > 0) {
  13. object = list.get(0);
  14. }
  15. 。。。。。。。。。
  16. }

MappedStatement.executeQueryWithCallback()方法包含了参数值映射、sql准备和sql执行等关键过程

  1. protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults)
  2. throws SQLException {
  3. //预先封装错误信息,如果报错时便于排查问题
  4. ErrorContext errorContext = request.getErrorContext();
  5. errorContext.setActivity("preparing the mapped statement for execution");
  6. errorContext.setObjectId(this.getId());
  7. errorContext.setResource(this.getResource());
  8. try {
  9. //验证入参
  10. parameterObject = validateParameter(parameterObject);
  11. //获取SQL对象
  12. Sql sql = getSql();
  13. errorContext.setMoreInfo("Check the parameter map.");
  14. // 入参映射
  15. ParameterMap parameterMap = sql.getParameterMap(request, parameterObject);
  16. errorContext.setMoreInfo("Check the result map.");
  17. //获取结果
  18. ResultMap resultMap = sql.getResultMap(request, parameterObject);
  19. request.setResultMap(resultMap);
  20. request.setParameterMap(parameterMap);
  21. errorContext.setMoreInfo("Check the parameter map.");
  22. //获取参数值
  23. Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject);
  24. errorContext.setMoreInfo("Check the SQL statement.");
  25. //获取拼装后的sql语句
  26. String sqlString = sql.getSql(request, parameterObject);
  27. errorContext.setActivity("executing mapped statement");
  28. errorContext.setMoreInfo("Check the SQL statement or the result map.");
  29. RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler);
  30. //sql执行
  31. sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback);
  32. ....省略
  33. }

最后调用com.ibatis.sqlmap.engine.execution.SqlExecutor.executeQuery(RequestScope, Connection, String, Object[], int, int, RowHandlerCallback)

  1. public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException {
  2. ...省略
  3. PreparedStatement ps = null;
  4. ResultSet rs = null;
  5. setupResultObjectFactory(request);
  6. try {
  7. errorContext.setMoreInfo("Check the SQL Statement (preparation failed).");
  8. Integer rsType = request.getStatement().getResultSetType();
  9. //初始化PreparedStatement,设置sql、参数值等
  10. if (rsType != null) {
  11. ps = prepareStatement(request.getSession(), conn, sql, rsType);
  12. } else {
  13. ps = prepareStatement(request.getSession(), conn, sql);
  14. }
  15. setStatementTimeout(request.getStatement(), ps);
  16. Integer fetchSize = request.getStatement().getFetchSize();
  17. if (fetchSize != null) {
  18. ps.setFetchSize(fetchSize.intValue());
  19. }
  20. errorContext.setMoreInfo("Check the parameters (set parameters failed).");
  21. request.getParameterMap().setParameters(request, ps, parameters);
  22. errorContext.setMoreInfo("Check the statement (query failed).");
  23. //执行
  24. ps.execute();
  25. errorContext.setMoreInfo("Check the results (failed to retrieve results).");
  26. //结果集处理
  27. rs = handleMultipleResults(ps, request, skipResults, maxResults, callback);
  28. 。。。省略
  29. }

ibatis源码分析的更多相关文章

  1. 【异常及源码分析】org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping

    一.异常出现的场景 1)异常出现的SQL @Select("SELECT\n" + " id,discount_type ,min_charge, ${cardFee} ...

  2. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  3. MyBatis源码分析(5)——内置DataSource实现

    @(MyBatis)[DataSource] MyBatis源码分析(5)--内置DataSource实现 MyBatis内置了两个DataSource的实现:UnpooledDataSource,该 ...

  4. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  5. 《深入理解Spark:核心思想与源码分析》(前言及第1章)

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

  6. 《深入理解Spark:核心思想与源码分析》一书正式出版上市

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

  7. 《深入理解Spark:核心思想与源码分析》正式出版上市

    自己牺牲了7个月的周末和下班空闲时间,通过研究Spark源码和原理,总结整理的<深入理解Spark:核心思想与源码分析>一书现在已经正式出版上市,目前亚马逊.京东.当当.天猫等网站均有销售 ...

  8. MyBatis源码分析(1)-MapConfig文件的解析

    1.简述 MyBatis是一个优秀的轻ORM框架,由最初的iBatis演化而来,可以方便的完成sql语句的输入输出到java对象之间的相互映射,典型的MyBatis使用的方式如下: String re ...

  9. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  10. 【MyBatis源码分析】Configuration加载(下篇)

    元素设置 继续MyBatis的Configuration加载源码分析: private void parseConfiguration(XNode root) { try { Properties s ...

随机推荐

  1. 又一个Rust练手项目-wssh(SSH over Websocket Client)

    原文地址https://blog.fanscore.cn/a/61/ 1. wssh 1.1 开发背景 公司内部的发布系统提供一个连接到k8s pod的web终端,可以在网页中连接到k8s pod内. ...

  2. 安全 – CSP (Content Security Policy)

    前言 之前讲过 CSRF.防 Cookie hacking 的. 也介绍过防 XSS 的 HtmlSanitizer. 今天再介绍 CSP. 参考 Content Security Policy 介绍 ...

  3. Identity – Introduction & Scaffold

    主要参考: Introduction to Identity on ASP.NET Core Start by command dotnet new webapp --auth Individual ...

  4. 如何基于Java解析国密数字证书

    一.说明 随着信息安全的重要性日益凸显,数字证书在各种安全通信场景中扮演着至关重要的角色.国密算法,作为我国自主研发的加密算法标准,其应用也愈发广泛.然而,在Java环境中解析使用国密算法的数字证书时 ...

  5. linux、unix软链接注意事项

    前言 在使用linux过程中,经常使用到软链接(类似windows快捷方式): 创建软链接之后,删除时不注意就会出现到问题 先说结论 删除软链接,确实是使用rm进行删除:但是有个小细节必须要特别注意! ...

  6. async/await和Grand Central Dispatch代码切换

    很多iOS开发开始学习结构化并发时已经用过了很多年Grand Central Dispatch,虽然从思想上二者区别很大,但是利用熟悉的东西去理解新的事物有助于提升学习理解的效率,接下来是这Grand ...

  7. c++中字符/串->整数

    char字符->整数数字:std::isdigit 用于判断某个字符是否为数字(0-9). 字符串->数字:std::stoi 用于将字符转换为整数. int isdigit( int c ...

  8. 逆向WeChat(七)

    上篇介绍了如何通过嗅探MojoIPC抓包小程序的HTTPS数据. 本篇逆向微信客户端本地数据库相关事宜. 本篇在博客园地址https://www.cnblogs.com/bbqzsl/p/184235 ...

  9. 数据库周刊54丨2020 年度报告:PingCAP、腾讯云数据库、人大金仓、GoldenDB ;CPU 100% SQL优化案例;Mysql内存溢出处理;sql server PK openGauss;Oracle 巡检说明书;避免删库跑路黑天鹅……

    热门资讯 1.PingCAP 2020 年度报告|相信开放的力量 [摘要]本文为PingCAP 2020年度报告.盘点了PingCAP里程碑大事件:完成D轮2.7亿美元融资,创造全球数据库历史新的里程 ...

  10. 新建 Blazor 项目 WebAssembly