范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

MyBatis系列教程五一文读懂Mybatis的执行流程

  在前面的章节中系统学习了Mybatis框架的使用,但是学习框架并不能只停留于如何使用的阶段,需要深入去了解Mybatis的执行流程。在本章节内,将详细了解Mybatis的实现细节。
  根据前面的章节,可以总结出Mybatis执行流程主要分为以下4个阶段:获取SQLSessionFactory获取SQLSession执行SQL语句
  下面就3个阶段进行分析。5.1 创建SqlSessionFactroy
  在创建SqlSessionFactory对象之前,需要先创建SqlSessionFactoryBuilder对象,该对象的主要作用就是解析Mybatis的全局配置文件并将配置文件抽象为Configuration对象。下面就从以下代码入手,了解该过程。public static void main( String[] args ) throws FileNotFoundException {   InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml");   //1.进入创建SqlSessionFactory流程   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);   SqlSession session = sqlSessionFactory.openSession(); }
  从注释1处入手,代码如下:public SqlSessionFactory build(InputStream inputStream) {     //可以看出此处调用了重载方法。     return build(inputStream, null, null); }  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {     try {       //1.此处解析xml配置文件       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);       //2.先调用了parse.parse()方法,该方法会将XML的配置抽象为Configuration对象       //3.调用build方法返回DefaultSessionFactory对象       return build(parser.parse());     } catch (Exception e) {       throw ExceptionFactory.wrapException("Error building SqlSession.", e);     } finally {       ErrorContext.instance().reset();       try {         inputStream.close();       } catch (IOException e) {         // Intentionally ignore. Prefer previous error.       }     }   } //忽略解析的细节可以看出该方法的作用就是将XML文件解析为Configuration对象 public Configuration parse() {     if (parsed) {       throw new BuilderException("Each XMLConfigBuilder can only be used once.");     }     parsed = true;     parseConfiguration(parser.evalNode("/configuration"));     return configuration; } public SqlSessionFactory build(Configuration config) {     return new DefaultSqlSessionFactory(config); }
  至此,已经大致了解了创建SqlSessionFactory的流程,过程如下:SqlSessionFactoryBuilder读取全局配置文件SqlSessionFactory读取配置文件后解析配置文件将配置文件解析为Configuration对象SqlSessionFactory通过Configuration对象创建了DefaultSQLSessionFactory对象
  以上过程的时序图如下:5.2 创建SqlSession
  当创建完SqlSessionFactroy后,可以通过SqlSessionFactory的openSession方法创建SqlSession对象。SqlSession session = sqlSessionFactory.openSession();
  继续查看openSession源码。
  DefaultSqlSessionFactory:public SqlSession openSession() {     return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);  } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {     Transaction tx = null;     try {       //通过Configuration对象获取数据源和事务配置信息       final Environment environment = configuration.getEnvironment();       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);       //获取sql语句执行器,从表面上看是SqlSession执行了sql语句,其实是Executor       //而Executor实际上是对JDBC中Statement对象的封装       final Executor executor = configuration.newExecutor(tx, execType);       return new DefaultSqlSession(configuration, executor, autoCommit);     } catch (Exception e) {       closeTransaction(tx); // may have fetched a connection so lets call close()       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);     } finally {       ErrorContext.instance().reset();     }   }
  在执行完以上方法后就可获得SqlSession对象,这个过程比较简单,需要注意的是Mybatis中真正执行sql语句的对象并不是SqlSession,而是Executor对象。接下来将进入最重要的环节,执行sql语句。
  5.3 执行Sql语句
  在经历前面两步的过程后,已经获取到SqlSession对象了,接下来,以查询为例查看Sql语句的具体执行流程。查询代码如下: public static void main( String[] args ) throws FileNotFoundException {     InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml");     SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);     SqlSession sqlSession = sqlSessionFactory.openSession();      UserMapper mapper = sqlSession.getMapper(UserMapper.class);     Listlist = mapper.findAllUser();  }
  从上面的代码可以看出直接调用了mapper中定义的findAllUser()方法,但是mapper是一个接口,findAllUser又是一个抽象方法,并没有方法体,那么Mybaits是怎么执行的呢?在这里需要先了解MapperProxy。这个类的作用是为Mapper接口生成代理类,也就是子类对象。5.3.1MapperProxy
  首先从sqlSession.getMapper()方法入手,从前面的代码中可以知道此时的sqlSession,实际上是DefaultSqlSession对象,因此进入DefaultSqlSession对象中的getMapper()方法,代码如下:
  DefaultSqlSession:@Override public  T getMapper(Class type) {     return configuration.getMapper(type, this); }
  从代码中可以看出该方法仅仅是从Configuration对象中读取了配置。接下来进入configuration.getMapper()方法,代码如下:
  Configuration:public  T getMapper(Class type, SqlSession sqlSession) {     return mapperRegistry.getMapper(type, sqlSession);  }
  在Configuration对象的getMapper方法中调用了mapperRegistry的getMapper()方法,继续进入MapperRegistry的getMapper()方法。
  MapperRegistry:public  T getMapper(Class type, SqlSession sqlSession) {     //首先创建代理工厂类对象mapperProxyFactory     final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);     if (mapperProxyFactory == null) {       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");     }     try {       //创建代理类       return mapperProxyFactory.newInstance(sqlSession);     } catch (Exception e) {       throw new BindingException("Error getting mapper instance. Cause: " + e, e);     }   }
  继续进入mapperProxyFactory.newInstance()方法。
  MapperProxyFactorypublic T newInstance(SqlSession sqlSession) {     final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);     return newInstance(mapperProxy); } protected T newInstance(MapperProxy mapperProxy) {     //在这里创建接口的实现类     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
  在经历了以上步骤后,Mybaits生成了Mapper接口的代理类,接下来就可以执行sql语句了。
  5.3.2 Executor
  此时执行findAllUser()方法时,Mybatis会进入MapperProxy的invoke()方法,进入此方法查看源码:
  MapperProxy:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {     try {       if (Object.class.equals(method.getDeclaringClass())) {         return method.invoke(this, args);       } else {         //代码会执行到此处         return cachedInvoker(method).invoke(proxy, method, args, sqlSession);       }     } catch (Throwable t) {       throw ExceptionUtil.unwrapThrowable(t);     }   } public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {     //交由mapperMethod.execute()方法     return mapperMethod.execute(sqlSession, args); }
  继续查看MapperMethod的execute()方法:
  MapperMethod:public Object execute(SqlSession sqlSession, Object[] args) {     Object result;     //判断SQL语句类型     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;   }
  可以看出在这个方法中主要对SQL语句所做的操作进行了判断,并且执行对用的操作。在示例中我们查询了所有的用户,因此会进入executeForMany()方法。
  MapperMethod:private  Object executeForMany(SqlSession sqlSession, Object[] args) {     List result;     Object param = method.convertArgsToSqlCommandParam(args);     //1.判断SQL有没有分页     if (method.hasRowBounds()) {       RowBounds rowBounds = method.extractRowBounds(args);       result = sqlSession.selectList(command.getName(), param, rowBounds);     } else {       //2.没有分页,因此代码执行到此处       result = sqlSession.selectList(command.getName(), param);     }
  在该方法中判断SQL语句是否有分页,因为示例中没有分页,因此代码会执行到注释2处。继续查看此方法,会发现调用了SqlSession的selectList()方法。
  DefaultSqlSession:public  List selectList(String statement, Object parameter) {     return this.selectList(statement, parameter, RowBounds.DEFAULT);   }
  继续进入重载的selectList()方法:
  DefaultSqlSession:public  List selectList(String statement, Object parameter, RowBounds rowBounds) {     try {       //1.从Configuration中获取MappedStatements,MappedStatements即Mapper文件抽象的对象       MappedStatement ms = configuration.getMappedStatement(statement);       //2.调用query方法       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);     } catch (Exception e) {       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);     } finally {       ErrorContext.instance().reset();     }   }
  在该方法中获取了MappedStatement后调用了query()方法,继续进入该方法。因为在示例中并未开启二级缓存,因此在此处直接调用BaseExecutor的方法。
  BaseExecutor:public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {     BoundSql boundSql = ms.getBoundSql(parameter);     CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);     return query(ms, parameter, rowBounds, resultHandler, key, boundSql);   }
  继续进入query()方法:
  BaseExecutor:public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());     if (closed) {       throw new ExecutorException("Executor was closed.");     }     if (queryStack == 0 && ms.isFlushCacheRequired()) {       clearLocalCache();     }     List list;     try {       queryStack++;       list = resultHandler == null ? (List) localCache.getObject(key) : null;       if (list != null) {         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);       } else {         //1.核心代码在此处         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);       }     } finally {       queryStack--;     }     if (queryStack == 0) {       for (DeferredLoad deferredLoad : deferredLoads) {         deferredLoad.load();       }       // issue #601       deferredLoads.clear();       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {         // issue #482         clearLocalCache();       }     }     return list;   }
  进入此query方法后,核心查询在注释1处,继续进入queryFromDatabase()方法。
  BaseExecutor:private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {     List list;     localCache.putObject(key, EXECUTION_PLACEHOLDER);     try {       //1.本质上时调用了这个方法       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);     } finally {       localCache.removeObject(key);     }     localCache.putObject(key, list);     if (ms.getStatementType() == StatementType.CALLABLE) {       localOutputParameterCache.putObject(key, parameter);     }     return list;   }
  可以看出,在此方法内调用了doQuery()方法:  @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);       stmt = prepareStatement(handler, ms.getStatementLog());       //1.最终会调用query方法。       return handler.query(stmt, resultHandler);     } finally {       closeStatement(stmt);     }   }
  继续进入注释1处的query方法,代码最终会执行到PreparedStatementHandler中的query()方法,代码如下:  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {     PreparedStatement ps = (PreparedStatement) statement;     ps.execute();     return resultSetHandler.handleResultSets(ps);   }
  代码执行到此处已经是我们所熟悉的JDBC了,在执行完SQL语句后,接下来就要对查询结果进行解析。整个过程的时序图如下:
  5.4 小结
  总结上述流程可以梳理为以下步:加载配置文件解析为Configuration对象使用Configuration对象获取DefaultSqlSessionFactory对象通过DefaultSqlSessionFactory对象获取DefaultSqlSession对象通过DefaultSqlSession创建MapperProxy对象MapperProxy调用MapperMthod方法判断SQL操作类型判断是否开启缓存二级缓存将SQL语句交由BaseExecutor处理判断在一级缓存中是否可以命中SQL语句执行结果,如果有则返回结果如果没有数据在调用RoutingStatemenHandler选择SQL语句执行方式默认情况下,MappedStatement为预编译执行,所以使用PrepareStatementHandler最后通过DefaultResultSetHandler封装结果给客户端
  整个过程流程图如下:
  MyBatis执行流程
  总结
  在本文内初步了解了MyBatis框架执行SQL语句的流程,MyBatis框架源码将会在下一个专栏内更新,敬请期待!!!

71岁老太出演狂飙,流不出泪哭不出声,演技却获赞杨健准备跑路了,临走之前,他回了趟家,母亲为他煮了一碗挂面。虽然杨健对于自己的事一字未提,但他一句妈,你陪我吃点,母亲就明白了。他不说,她便不问。不敢在儿子面前掉眼泪,躲进厨房,看狂飙唯一输家最牛星二代王骁跌下神坛,演技尴尬,全程被吊打狂飙爆了,几乎所有人都吃到了红利。张颂文从此跃居实力派一线,张译的战绩里再添一笔光辉。大嫂高叶火了,女主李一桐热度上升,高启兰小五也让人记住了脸。李健终于以演员的身份,有了自己的名市场观潮丨宠物产业向专业化品牌化发展市场观潮原标题宠物产业向专业化品牌化发展工人日报中工网记者赵昂春节假期刚刚过去,今年各大平台的年货消费数据也陆续出炉。一个很明显的消费现象是,宠物相关的年货搜索量大增,从宠物年夜饭新年第一炮!贺岁杯王天一称霸1月31日,2023深圳中国银行贺岁杯象棋特级大师快棋邀请赛剋完,王天一冠军,奖金8万,亚军孟辰,奖金5万。昨晚,天王洪智落后情况下,反败为胜,拿下蒋川,获得第三名。上午九点,决赛顶级杀器回归,新疆队实力排进前四,距离争冠仅差最后唯一短板头条创作挑战赛前段时间,周琦从澳洲NBL请假回国,引发了人们周琦要回新疆队的猜测。尽管现在周琦回归没有了下文,但新疆队却真的迎来了不比周琦差的顶级大杀器回归。有了他,新疆队实力大增通信行业发明专利排行榜前五被手机厂商包揽,OPPO排名令人意外近期,权威机构对通信领域的中国本土公司就最近两年发明专利的申请量授权量市场价值引用情况进行了统计分析。如下图所示,2021年和2022年中国通信行业专利发明申请量排行榜,前五名均被高质量发展东莞证券打造服务实体经济东证样板日前,东莞证券股份有限公司(简称东莞证券)组织公司党委班子经理层班子召开传达学习省市高质量发展大会精神专题会议,由东莞证券党委书记董事长陈照星主持,传达学习了全省高质量发展大会及东曹圭成旅欧梦破灭,日本球员更易留洋,日韩差异由此而起卡塔尔世界杯时,亚洲球队令人眼前一亮,日本队及韩国队尤其成为热话。韩国队虽有孙兴慜金玟哉黄喜灿李康仁等球星压阵,但论平均实力,日本明显更高一筹且更稳定,阵中能在欧洲顶级联赛站稳阵脚2米21华裔入围全美最佳球员观察名单日后中国男篮内线新倚仗?北京时间1月31日,为普渡大学效力的华裔中锋周志豪,成功入围伍登奖全美最佳球员赛季中期的20人观察名单,他也有望迎来辉煌的赛季。对于这样一位可塑之才,也同样将是中国男篮日后努力归化谁是中国最强场地赛选手?众所周知,奥运会亚运会全运会等综合性大赛的田径比赛都是在室外400米标准田径场进行,室内田径赛则是在全封闭的室内场馆(一圈200米)进行。相比于室外400米标准田径场,室内田径赛的拿下澳网十冠,争议缠上德约科维奇来源环球时报环球时报特约记者李佳寅在历史性地拿下个人澳网第十冠后,围绕德约科维奇纪录与争议的讨论仍在继续。雅虎体育29日称,比赛结束后,情绪激动的德约科维奇前往球员包厢与母亲分享胜