发表于: 2017-12-18 19:44:51

1 642



今天完成的事


书接上回。

从MyBatis一次select 查询语句来分析MyBatis的架构设计
一、数据准备
   1. 准备数据库数据

2. 配置Mybatis的配置文件,命名为mybatisConfig.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC" />
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver" />
               <property name="url" value="jdbc:mysql://47.95.7.57:3306/demo" />
               <property name="username" value="root" />
               <property name="password" value="Gao4651016" />
           </dataSource>
       </environment>
   </environments>
   <mappers>
       <mapper  resource="dao/mapper.xml   "/>
   </mappers>
</configuration>

实体Bean 以及配置Mapper配置文件

/**
* Created by ${MIND-ZR} on 2017/12/18.
*/
public class User {
private int id;
   private String m_name;
   private  int age;


   public int getId() {
return id;
   }

public void setId(int id) {
this.id = id;
   }

public String getM_name() {
return m_name;
   }

public void setM_name(String m_name) {
this.m_name = m_name;
   }

public int getAge() {
return age;
   }

public void setAge(int age) {
this.age = age;
   }


@Override
   public String toString() {
return "User{" +
"id=" + id +
", m_name='" + m_name + '\'' +
", age=" + age +
'}';
   }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.Mapper">

   <select id="selectById" resultType="User" parameterType="java.lang.Integer">
           SELECT id ,m_name , m_age FROM MybatisDemo WHERE id =#{id}
</select>

</mapper>
二、SqlSession 的工作过程分析:
1. 开启一个数据库访问会话---创建SqlSession对象:
   MyBatis使用SqlSession封装了对数据库的访问,把对数据库的会话和事务控制放到了SqlSession对象中。
2. 为SqlSession传递一个配置的Sql语句 的Statement Id和参数,然后返回结果:
上面的selectById是配置在mapper文件中的方法ID(Statement id)。params 是传递的查询参数。

我们来看一下sqlSession.selectOne方法的定义。(查看源码)

追溯到下面的代码。


public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
   List var6;

   try {

  //1.根据Statement Id,在mybatis 配置对象Configuration中查找和配置文件相对应的MappedStatement      

       MappedStatement ms = this.configuration.getMappedStatement(statement);

 //2. 将查询任务委托给MyBatis 的执行器 Executor  

       List<E> result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
       var6 = result;
   } catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var10, var10);
   } finally {
       ErrorContext.instance().reset();
   }

return var6;
}

SqlSession根据Statement ID, 在mybatis配置对象Configuration中获取到对应的MappedStatement对象,然后调用mybatis执行器来执行具体的操作。


3.MyBatis执行器Executor根据SqlSession传递的参数执行query()方法

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

// 1.根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示  

   BoundSql boundSql = ms.getBoundSql(parameter);

   // 2.为当前的查询创建一个缓存Key  

   CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
   return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> 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(this.closed) {
throw new ExecutorException("Executor was closed.");
   } else {
if(this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
       }

       List list;
       try {
           ++this.queryStack;
           list = resultHandler == null?(List)this.localCache.getObject(key):null;
           if(list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

           } else {

        // 3.缓存中没有值,直接从数据库中读取数据  

               list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
           }
       } finally {
           --this.queryStack;
       }

if(this.queryStack == 0) {
           Iterator i$ = this.deferredLoads.iterator();

           while(i$.hasNext()) {
               BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
               deferredLoad.load();
           }

this.deferredLoads.clear();
           if(this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
           }
       }

return list;
   }
}


private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

   List list;

   try {

    //4. 执行查询,返回List 结果,然后将查询的结果放入缓存之中

       list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
   } finally {
this.localCache.removeObject(key);
   }

this.localCache.putObject(key, list);
   if(ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
   }

return list;
}

点进去doQuery

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
   Statement stmt = null;

   List var9;
   try {

       Configuration configuration = ms.getConfiguration();

     //5. 根据既有的参数,创建StatementHandler对象来执行查询操作  

       StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
       stmt = this.prepareStatement(handler, ms.getStatementLog());
       var9 = handler.query(stmt, resultHandler);
   } finally {
this.closeStatement(stmt);
   }

return var9;
}
上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集。
从上面的代码中我们可以看出,Executor的功能和作用是:
(1、根据传递的参数,完成SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用;
(2、为查询创建缓存,以提高性能(这里先不说,看另一个笔记);
(3、创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果。
4. StatementHandler对象负责设置Statement对象中的查询参数、处理JDBC返回的resultSet,将resultSet加工为List 集合返回:
 接着上面的Executor第六步,看一下:prepareStatement() 方法的实现:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {

 // 1.调用preparedStatemnt。execute()方法,然后将resultSet交给ResultSetHandler处理  

PreparedStatement ps = (PreparedStatement)statement;

   ps.execute();

  //2. 使用ResultHandler来处理ResultSet  

   return this.resultSetHandler.handleResultSets(ps);
}

再点进去handleResultSets

public List<Object> handleResultSets(Statement stmt) throws SQLException {
   List<Object> multipleResults = new ArrayList();
   List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
   int resultMapCount = resultMaps.size();
   int resultSetCount = 0;
   ResultSet rs = stmt.getResultSet();

   while(rs == null) {
if(stmt.getMoreResults()) {
           rs = stmt.getResultSet();
       } else if(stmt.getUpdateCount() == -1) {
break;
       }
   }

this.validateResultMapsCount(rs, resultMapCount);

   while(rs != null && resultMapCount > resultSetCount) {
       ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);

       FastResultSetHandler.ResultColumnCache resultColumnCache = new FastResultSetHandler.ResultColumnCache(rs.getMetaData(), this.configuration);

   //将resultSet  

       this.handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
       rs = this.getNextResultSet(stmt);
       this.cleanUpAfterHandlingResultSet();
       ++resultSetCount;
   }

从上述代码我们可以看出,StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的实现,是调用了ResultSetHandler的handleResultSets(Statement) 方法。ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句执行后生成的resultSet 结果集转换成List<E> 结果集。。。


本期代码解读就到这里。敬请下次日报,想写个mybatis缓存的源码解析。


收获

很大

遇到的问题

源码看起来是真费劲啊。

明天的计划

接着回顾


返回列表 返回列表
评论

    分享到