mybatis源码解读(五)——sql语句的执行流程_mybatis源码中增删改查在执行sql语句的时候区别及过程<b-程序员宅基地

技术标签: Mybatis详解  java  数据库  sql  

  还是以第一篇博客中给出的例子,根据代码实例来入手分析。

 1     static {
 2         InputStream inputStream = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-configuration.xml");
 3         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 4     }
 5 
 6     /**
 7      * 查询单个记录
 8      */
 9     @Test
10     public void testSelectOne() {
11         SqlSession session = sqlSessionFactory.openSession();
12         User user = session.selectOne(NAME_SPACE + ".selectUserById", 1);
13         System.out.println(user);
14         session.close();
15     }

  如何加载配置文件前面也已经介绍了,通过配置文件产生SqlSessionFactory,追溯源码可以发现其实现是 DefaultSqlSessionFactory。

1   public SqlSessionFactory build(Configuration config) {
2     return new DefaultSqlSessionFactory(config);
3   }

  得到 SqlSessionFactory 之后,就可以通过 SqlSessionFactory 去获取 SqlSession 对象。源码如下:

 1     @Override
 2     public SqlSession openSession() {
 3         return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
 4     }
 5 
 6 
 7     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 8         Transaction tx = null;
 9         try {
10             //Environment对象封装了配置文件中对于数据源和事务的配置
11             final Environment environment = configuration.getEnvironment();
12             final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
13             tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
14             //获取Executor对象,用来执行sql语句
15             final Executor executor = configuration.newExecutor(tx, execType);
16             return new DefaultSqlSession(configuration, executor, autoCommit);
17         } catch (Exception e) {
18             closeTransaction(tx); // may have fetched a connection so lets call close()
19             throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
20         } finally {
21             ErrorContext.instance().reset();
22         }
23     }

  这里我们重点看一下第 15 行代码:

 1   public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 2     executorType = executorType == null ? defaultExecutorType : executorType;
 3     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
 4     Executor executor;
 5     if (ExecutorType.BATCH == executorType) {
 6       executor = new BatchExecutor(this, transaction);
 7     } else if (ExecutorType.REUSE == executorType) {
 8       executor = new ReuseExecutor(this, transaction);
 9     } else {
10       executor = new SimpleExecutor(this, transaction);
11     }
12     if (cacheEnabled) {
13       executor = new CachingExecutor(executor);
14     }
15     executor = (Executor) interceptorChain.pluginAll(executor);
16     return executor;
17   }

  根据执行器类型这里有多种不同的执行器Executor。

  注意第 12 行代码,如果我们开启了缓存,即 cacheEnabled = true(这里是一级缓存,默认是开启的),第13行代码使用了装饰器模式,在原有的 Executor 上装饰了缓存功能。

  第 15 行用于设置插件。

  这时候已经得到SqlSession对象了,实际类型是 DefaultSqlSession。接下来我们就可以通过该对象来执行sql语句了。

 1、insert 操作

 1     /**
 2      * 插入一条记录
 3      */
 4     @Test
 5     public void testInsert() {
 6         SqlSession session = sqlSessionFactory.openSession();
 7         User user = new User(2, "zhangsan", 22);
 8         session.insert(NAME_SPACE + ".insertUser", user);
 9         session.commit();
10         session.close();
11     }

  通过第8行代码,我们进入到 insert 方法:

1   @Override
2   public int insert(String statement, Object parameter) {
3     return update(statement, parameter);
4   }

  注意:这里通过 insert 方法,调用的是 update 方法。

 1   public int update(String statement, Object parameter) {
 2     try {
 3       dirty = true;
 4       MappedStatement ms = configuration.getMappedStatement(statement);
 5       return executor.update(ms, wrapCollection(parameter));
 6     } catch (Exception e) {
 7       throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
 8     } finally {
 9       ErrorContext.instance().reset();
10     }
11   }

  第4行根据给的statement参数,获取配置的所有如下信息,并将其封装到 MappedStatement 对象中,关于这个对象后面会详细介绍。

1     <!-- 向 user 表插入一条数据 -->
2     <insert id="insertUser" parameterType="com.ys.po.User" >
3         insert into
4         user(<include refid="Base_Column_List" />)
5         value(#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})
6     </insert>

  ①、接着我们看第 5 行代码,首先看 wrapCollection(parameter) 方法:

 1     private Object wrapCollection(final Object object) {
 2         if (object instanceof Collection) {
 3             DefaultSqlSession.StrictMap<Object> map = new DefaultSqlSession.StrictMap<Object>();
 4             map.put("collection", object);
 5             if (object instanceof List) {
 6                 map.put("list", object);
 7             }
 8             return map;
 9         } else if (object != null && object.getClass().isArray()) {
10             DefaultSqlSession.StrictMap<Object> map = new DefaultSqlSession.StrictMap<Object>();
11             map.put("array", object);
12             return map;
13         }
14         return object;
15     }

  通过这段代码的if-else if 语句主要做了如下两个操作:

  1、如果传入的参数是集合 Collection,在 map 集合中放入一个key为"collection"、value为参数的键值对,接着判断该集合是不是 List 类型,如果是,那么在 map 集合中在放入一个key为"list"、value为参数的键值对。

  2、如果传入的参数是数组类型,那么在 map 中放入一个key为"array"、value为参数的键值对。

  注意:这里的 StrictMap ,其实就是一个 HashMap。

 1   public static class StrictMap<V> extends HashMap<String, V> {
 2 
 3     private static final long serialVersionUID = -5741767162221585340L;
 4 
 5     @Override
 6     public V get(Object key) {
 7       if (!super.containsKey(key)) {
 8         throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
 9       }
10       return super.get(key);
11     }
12 
13   }
14 
15 }
View Code

  ②、wrapCollection(parameter) 方法介绍完了。接着我们看 executor.update()方法:

  这里需要说明的是 Executor 对象上面我们已经介绍了,由于默认是开启一级缓存的,这时候我们进入 CachingExecutor 类的 update() 方法:

1   public int update(MappedStatement ms, Object parameterObject) throws SQLException {
2     flushCacheIfRequired(ms);
3     return delegate.update(ms, parameterObject);
4   }

  首先我们看这里的第 2 行代码:

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {      
      tcm.clear(cache);
    }
  }

  这里表示的意思是是否清除缓存。看我们是否在配置文件中配置了 <cache> 标签,以及我们是否在 <insert /> 标签中是否增加了 flushCache="true"属性。如果有其中任何一个,此次操作都会清除缓存。

  接着我们再看第3行代码,这里的delegate 是 Executor,但是这是一个接口,其真实类型是SimpleExecutor,经过装饰器模式,调用 CachingExecutor 的 update 方法,经过处理后,最后最后调用 SimpleExecutor的update方法:

  具体调用:

  首先调用 BaseExecutor 的 update 方法

  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

  然后调用 doUpdate 方法,由于 SimpleExecutor 继承 BaseExecutor 类,并重写了 doUpdate 方法,我们看 SimpleExecutor 类的 doUpdate 方法:

 1   public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.update(stmt);
 8     } finally {
 9       closeStatement(stmt);
10     }
11   }

  看到这里,Statement 对象,看到我们熟悉的 JDBC 操作数据库的对象了吧。我们直接看第 6 行代码:

1   private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
2     Statement stmt;
3     Connection connection = getConnection(statementLog);
4     stmt = handler.prepare(connection, transaction.getTimeout());
5     handler.parameterize(stmt);
6     return stmt;
7   }

  第 3 行代码获取数据库连接,是根据前面配置的数据源来获取。接着我们看 handler.update(stemt) 方法:

 1   public int update(Statement statement) throws SQLException {
 2     String sql = boundSql.getSql();
 3     Object parameterObject = boundSql.getParameterObject();
 4     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
 5     int rows;
 6     if (keyGenerator instanceof Jdbc3KeyGenerator) {
 7       statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
 8       rows = statement.getUpdateCount();
 9       keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
10     } else if (keyGenerator instanceof SelectKeyGenerator) {
11       statement.execute(sql);
12       rows = statement.getUpdateCount();
13       keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
14     } else {
15       statement.execute(sql);
16       rows = statement.getUpdateCount();
17     }
18     return rows;
19   }

  这里就都是我们熟悉的 JDBC 操作了。

2、update 和 delete 操作

 1     /**
 2      * 更新一条记录
 3      */
 4     @Test
 5     public void testUpdate() {
 6         SqlSession session = sqlSessionFactory.openSession();
 7         User user = new User(2, "lisi", 22);
 8         session.update(NAME_SPACE + ".updateUserById", user);
 9         session.commit();
10         session.close();
11     }
12 
13     /**
14      * 删除一条记录
15      */
16     @Test
17     public void testDelete() {
18         SqlSession session = sqlSessionFactory.openSession();
19         session.delete(NAME_SPACE + ".deleteUserById", 2);
20         session.commit();
21         session.close();
22     }

  进入到上述第 8 行和 第 19 行代码,我们发现都是进入到和 上面 insert 操作一样的代码:

  第 8 行:

  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  第 19 行:

  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

  之后的 update 也是上面的代码。这也和我们理解的应该保持一致。

  结论:

insert、update、delete都是属于对数据库的行进行更新操作

  所以这三种语句的执行都是采用的同种逻辑处理。最终都可以调用 executeUpdate() 方法来处理。唯一不同的是 select 操作,必须要调用 executeQuery() 来执行。

3、select 操作

 1     /**
 2      * 查询单个记录
 3      */
 4     @Test
 5     public void testSelectOne() {
 6         SqlSession session = sqlSessionFactory.openSession();
 7         User user = session.selectOne(NAME_SPACE + ".selectUserById", 1);
 8         System.out.println(user);
 9         session.close();
10     }
11 
12     /**
13      * 查询多个记录
14      */
15     @Test
16     public void testSelectList() {
17         SqlSession session = sqlSessionFactory.openSession();
18         List<User> listUser = session.selectList(NAME_SPACE + ".selectUserAll");
19         if (listUser != null) {
20             System.out.println(listUser.size());
21         }
22         session.close();
23     }

  首先看第 7 行代码:

 1   public <T> T selectOne(String statement, Object parameter) {
 2     // Popular vote was to return null on 0 results and throw exception on too many.
 3     List<T> list = this.<T>selectList(statement, parameter);
 4     if (list.size() == 1) {
 5       return list.get(0);
 6     } else if (list.size() > 1) {
 7       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
 8     } else {
 9       return null;
10     }
11   }

  看到上面的第 3 行代码,我们可能马上就明白了,其实selectOne() 和 selectList() 也都是调用的 selectList() 方法,只不过 selectOne() 是获取集合的第一个元素而已。

  接着看 selectList() 源码:

 1   @Override
 2   public <E> List<E> selectList(String statement, Object parameter) {
 3     return this.selectList(statement, parameter, RowBounds.DEFAULT);
 4   }
 5 
 6   @Override
 7   public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
 8     try {
 9       MappedStatement ms = configuration.getMappedStatement(statement);
10       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
11     } catch (Exception e) {
12       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
13     } finally {
14       ErrorContext.instance().reset();
15     }
16   }

  看第10的 query 方法:

1   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
2     BoundSql boundSql = ms.getBoundSql(parameterObject);
3     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
4     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
5   }
 1   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 2       throws SQLException {
 3     Cache cache = ms.getCache();
 4     if (cache != null) {
 5       flushCacheIfRequired(ms);
 6       if (ms.isUseCache() && resultHandler == null) {
 7         ensureNoOutParams(ms, parameterObject, boundSql);
 8         @SuppressWarnings("unchecked")
 9         List<E> list = (List<E>) tcm.getObject(cache, key);
10         if (list == null) {
11           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
12           tcm.putObject(cache, key, list); // issue #578 and #116
13         }
14         return list;
15       }
16     }
17     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
18   }

  最后我们来到doQuery() 方法:

  public <E> List<E> 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);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
1   @Override
2   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
3     PreparedStatement ps = (PreparedStatement) statement;
4     ps.execute();
5     return resultSetHandler.<E> handleResultSets(ps);
6   }

  至此,select 操作也执行完毕了。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ysvae/article/details/81090877

智能推荐

如何配置filezilla服务端和客户端_filezilla server for windows (32bit x86)-程序员宅基地

文章浏览阅读7.8k次,点赞3次,收藏9次。如何配置filezilla服务端和客户端百度‘filezilla server’下载最新版。注意点:下载的版本如果是32位的适用xp和win2003,百度首页的是适用于win7或更高的win系统。32和64内容无异。安装过程也是一样的。一、这里的filezilla包括服务端和客户端。我们先来用filezilla server 架设ftp服务端。看步骤。1选择标准版的就可以了。 _filezilla server for windows (32bit x86)

深度学习图像处理01:图像的本质-程序员宅基地

文章浏览阅读724次,点赞18次,收藏8次。深度学习作为一种强大的机器学习技术,已经成为图像处理领域的核心技术之一。通过模拟人脑处理信息的方式,深度学习能够从图像数据中学习到复杂的模式和特征,从而实现从简单的图像分类到复杂的场景理解等多种功能。要充分发挥深度学习在图像处理中的潜力,我们首先需要理解图像的本质。本文旨在深入探讨深度学习图像处理的基础概念,为初学者铺平通往高级理解的道路。我们将从最基础的问题开始:图像是什么?我们如何通过计算机来理解和处理图像?

数据探索阶段——对样本数据集的结构和规律进行分析_数据分析 规律集-程序员宅基地

文章浏览阅读62次。在收集到初步的样本数据之后,接下来该考虑的问题有:(1)样本数据集的数量和质量是否满足模型构建的要求。(2)是否出现从未设想过的数据状态。(3)是否有明显的规律和趋势。(4)各因素之间有什么样的关联性。解决方案:检验数据集的数据质量、绘制图表、计算某些特征量等,对样本数据集的结构和规律进行分析。从数据质量分析和数据特征分析两个角度出发。_数据分析 规律集

上传计算机桌面文件图标不见,关于桌面上图标都不见了这类问题的解决方法-程序员宅基地

文章浏览阅读8.9k次。关于桌面上图标都不见了这类问题的解决方法1、在桌面空白处右击鼠标-->排列图标-->勾选显示桌面图标。2、如果问题还没解决,那么打开任务管理器(同时按“Ctrl+Alt+Del”即可打开),点击“文件”→“新建任务”,在打开的“创建新任务”对话框中输入“explorer”,单击“确定”按钮后,稍等一下就可以见到桌面图标了。3、问题还没解决,按Windows键+R(或者点开始-->..._上传文件时候怎么找不到桌面图标

LINUX 虚拟网卡tun例子——修改_怎么设置tun的接收缓冲-程序员宅基地

文章浏览阅读1.5k次。参考:http://blog.csdn.net/zahuopuboss/article/details/9259283 #include #include #include #include #include #include #include #include #include #include #include #include _怎么设置tun的接收缓冲

UITextView 评论输入框 高度自适应-程序员宅基地

文章浏览阅读741次。创建一个inputView继承于UIView- (instancetype)initWithFrame:(CGRect)frame{ self = [superinitWithFrame:frame]; if (self) { self.backgroundColor = [UIColorcolorWithRed:0.13gre

随便推点

字符串基础面试题_java字符串相关面试题-程序员宅基地

文章浏览阅读594次。字符串面试题(2022)_java字符串相关面试题

VSCODE 实现远程GUI,显示plt.plot, 设置x11端口转发_vscode远程ssh连接服务器 python 显示plt-程序员宅基地

文章浏览阅读1.4w次,点赞12次,收藏21次。VSCODE 实现远程GUI,显示plt.plot, 设置x11端口转发问题服务器 linux ubuntu16.04本地 windows 10很多小伙伴发现VSCode不能显示figure,只有用自带的jupyter才能勉强个截图、或者转战远程桌面,这对数据分析极为不方便。在命令行键入xeyes(一个显示图像的命令)会failed,而桌面下会出现:但是Xshell能实现X11转发图像,有交互功能,但只能用Xshell输入命令plot,实在不方便。其实VScode有X11转发插件!!方法_vscode远程ssh连接服务器 python 显示plt

element-ui switch开关打开和关闭时的文字设置样式-程序员宅基地

文章浏览阅读3.3k次,点赞2次,收藏2次。element switch开关文字显示element中switch开关把on-text 和 off-text 属性改为 active-text 和 inactive-text 属性.怎么把文字描述显示在开关上?下面就是实现方法: 1 <el-table-column label="状态"> 2 <template slot-scope="scope">..._el-switch 不同状态显示不同字

HttpRequestUtil方法get、post、JsonToPost_httprequestutil.httpget-程序员宅基地

文章浏览阅读785次。java后台发起请求使用的工具类package com.cennavi.utils;import org.apache.http.Header;import org.apache.http.HttpResponse;import org.apache.http.HttpStatus;import org.apache.http.client.HttpClient;import org.apache.http.client.methods.HttpPost;import org.apach_httprequestutil.httpget

App-V轻量级应用程序虚拟化之三客户端测试-程序员宅基地

文章浏览阅读137次。在前两节我们部署了App-V Server并且序列化了相应的软件,现在可谓是万事俱备,只欠东风。在这篇博客里面主要介绍一下如何部署客户端并实现应用程序的虚拟化。在这里先简要的说一下应用虚拟化的工作原理吧!App-V Streaming 就是利用templateServer序列化出一个软件运行的虚拟环境,然后上传到app-v Server上,最后客户..._app-v 客户端