Mybatis源碼解析之執(zhí)行SQL語(yǔ)句

2022-12-13 11:12:56 來(lái)源:51CTO博客

作者:鄭志杰

mybatis 操作數(shù)據(jù)庫(kù)的過(guò)程


(資料圖)

// 第一步:讀取mybatis-config.xml配置文件InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");// 第二步:構(gòu)建SqlSessionFactory(框架初始化)SqlSessionFactory sqlSessionFactory = new  SqlSessionFactoryBuilder().bulid();// 第三步:打開(kāi)sqlSessionSqlSession session = sqlSessionFactory.openSession();// 第四步:獲取Mapper接口對(duì)象(底層是動(dòng)態(tài)代理)AccountMapper accountMapper = session.getMapper(AccountMapper.class);// 第五步:調(diào)用Mapper接口對(duì)象的方法操作數(shù)據(jù)庫(kù);Account account = accountMapper.selectByPrimaryKey(1);

通過(guò)調(diào)用 session.getMapper (AccountMapper.class) 所得到的 AccountMapper 是一個(gè)動(dòng)態(tài)代理對(duì)象,所以執(zhí)行accountMapper.selectByPrimaryKey (1) 方法前,都會(huì)被 invoke () 攔截,先執(zhí)行 invoke () 中的邏輯。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {        //  要執(zhí)行的方法所在的類(lèi)如果是Object,直接調(diào)用,不做攔截處理        if (Object.class.equals(method.getDeclaringClass())) {            return method.invoke(this, args);            //如果是默認(rèn)方法,也就是java8中的default方法        } else if (isDefaultMethod(method)) {            // 直接執(zhí)行default方法            return invokeDefaultMethod(proxy, method, args);        }    } catch (Throwable t) {        throw ExceptionUtil.unwrapThrowable(t);    }     // 從緩存中獲取MapperMethod    final MapperMethod mapperMethod = cachedMapperMethod(method);    return mapperMethod.execute(sqlSession, args);}

從 methodCache 獲取對(duì)應(yīng) DAO 方法的 MapperMethod

MapperMethod 的主要功能是執(zhí)行 SQL 語(yǔ)句的相關(guān)操作,在初始化的時(shí)候會(huì)實(shí)例化兩個(gè)對(duì)象:SqlCommand(Sql 命令)和 MethodSignature(方法簽名)。

/**   * 根據(jù)Mapper接口類(lèi)型、接口方法、核心配置對(duì)象 構(gòu)造MapperMethod對(duì)象   * @param mapperInterface   * @param method   * @param config   */  public MapperMethod(Class mapperInterface, Method method, Configuration config) {    this.command = new SqlCommand(config, mapperInterface, method);    // 將Mapper接口中的數(shù)據(jù)庫(kù)操作方法(如Account selectById(Integer id);)封裝成方法簽名MethodSignature    this.method = new MethodSignature(config, mapperInterface, method);  }

new SqlCommand()調(diào)用 SqlCommand 類(lèi)構(gòu)造方法:

public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {      // 獲取Mapper接口中要執(zhí)行的某個(gè)方法的方法名      // 如accountMapper.selectByPrimaryKey(1)      final String methodName = method.getName();      // 獲取方法所在的類(lèi)      final Class declaringClass = method.getDeclaringClass();      // 解析得到Mapper語(yǔ)句對(duì)象(對(duì)配置文件中的中的sql語(yǔ)句進(jìn)行封裝)      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,          configuration);      if (ms == null) {        if (method.getAnnotation(Flush.class) != null) {          name = null;          type = SqlCommandType.FLUSH;        } else {          throw new BindingException("Invalid bound statement (not found): "              + mapperInterface.getName() + "." + methodName);        }      } else {        // 如com.bjpowernode.mapper.AccountMapper.selectByPrimaryKey        name = ms.getId();        // SQL類(lèi)型:增 刪 改 查        type = ms.getSqlCommandType();        if (type == SqlCommandType.UNKNOWN) {          throw new BindingException("Unknown execution method for: " + name);        }      }    }
private MapperMethod cachedMapperMethod(Method method) {     MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);     if (mapperMethod == null) {         mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());         this.methodCache.put(method, mapperMethod);     }     return mapperMethod; }

調(diào)用 mapperMethod.execute (sqlSession, args)

在 mapperMethod.execute () 方法中,我們可以看到:mybatis 定義了 5 種 SQL 操作類(lèi)型:insert/update/delete/select/flush。其中,select 操作類(lèi)型又可以分為五類(lèi),這五類(lèi)的返回結(jié)果都不同,分別對(duì)應(yīng):

?返回參數(shù)為空:executeWithResultHandler ();

?查詢多條記錄:executeForMany (),返回對(duì)象為 JavaBean

?返參對(duì)象為 map:executeForMap (), 通過(guò)該方法查詢數(shù)據(jù)庫(kù),最終的返回結(jié)果不是 JavaBean,而是 Map

?游標(biāo)查詢:executeForCursor ();關(guān)于什么是游標(biāo)查詢,自行百度哈;

?查詢單條記錄: sqlSession.selectOne (),通過(guò)該查詢方法,最終只會(huì)返回一條結(jié)果;

通過(guò)源碼追蹤我們可以不難發(fā)現(xiàn):當(dāng)調(diào)用 mapperMethod.execute () 執(zhí)行 SQL 語(yǔ)句的時(shí)候,無(wú)論是insert/update/delete/flush,還是 select(包括 5 種不同的 select), 本質(zhì)上時(shí)通過(guò) sqlSession 調(diào)用的。在 SELECT 操作中,雖然調(diào)用了 MapperMethod 中的方法,但本質(zhì)上仍是通過(guò) Sqlsession 下的 select (), selectList (), selectCursor (), selectMap () 等方法實(shí)現(xiàn)的。

而 SqlSession 的內(nèi)部實(shí)現(xiàn),最終是調(diào)用執(zhí)行器 Executor(后面會(huì)細(xì)說(shuō))。這里,我們可以先大概看一下 mybatis 在執(zhí)行 SQL 語(yǔ)句的時(shí)候的調(diào)用過(guò)程:

以accountMapper.selectByPrimaryKey (1) 為例:

?調(diào)用 SqlSession.getMapper ():得到 xxxMapper (如 UserMapper) 的動(dòng)態(tài)代理對(duì)象;

?調(diào)用accountMapper.selectByPrimaryKey (1):在 xxxMapper 動(dòng)態(tài)代理內(nèi)部,會(huì)根據(jù)要執(zhí)行的 SQL 語(yǔ)句類(lèi)型 (insert/update/delete/select/flush) 來(lái)調(diào)用 SqlSession 對(duì)應(yīng)的不同方法,如 sqlSession.insert ();

?在 sqlSession.insert () 方法的實(shí)現(xiàn)邏輯中,又會(huì)轉(zhuǎn)交給 executor.query () 進(jìn)行查詢;

?executor.query () 又最終會(huì)轉(zhuǎn)交給 statement 類(lèi)進(jìn)行操作,到這里就是 jdbc 操作了。

有人會(huì)好奇,為什么要通過(guò)不斷的轉(zhuǎn)交,SqlSession->Executor->Statement,而不是直接調(diào)用 Statement 執(zhí)行 SQL 語(yǔ)句呢?因?yàn)樵谡{(diào)用 Statement 之前,會(huì)處理一些共性的邏輯,如在 Executor 的實(shí)現(xiàn)類(lèi) BaseExecutor 會(huì)有一級(jí)緩存相關(guān)的邏輯,在 CachingExecutor 中會(huì)有二級(jí)緩存的相關(guān)邏輯。如果直接調(diào)用 Statement 執(zhí)行 SQL 語(yǔ)句,那么在每個(gè) Statement 的實(shí)現(xiàn)類(lèi)中,都要寫(xiě)一套一級(jí)緩存和二級(jí)緩存的邏輯,就顯得冗余了。這一塊后面會(huì)細(xì)講。

// SQL命令(在解析mybatis-config.xml配置文件的時(shí)候生成的)  private final SqlCommand command;    public Object execute(SqlSession sqlSession, Object[] args) {    Object result;    // 從command對(duì)象中獲取要執(zhí)行操作的SQL語(yǔ)句的類(lèi)型,如INSERT/UPDATE/DELETE/SELECT    switch (command.getType()) {      // 插入      case INSERT: {        // 把接口方法里的參數(shù)轉(zhuǎn)換成sql能識(shí)別的參數(shù)        // 如:accountMapper.selectByPrimaryKey(1)        // 把其中的參數(shù)"1"轉(zhuǎn)化為sql能夠識(shí)別的參數(shù)        Object param = method.convertArgsToSqlCommandParam(args);        // sqlSession.insert(): 調(diào)用SqlSession執(zhí)行插入操作        // rowCountResult(): 獲取SQL語(yǔ)句的執(zhí)行結(jié)果        result = rowCountResult(sqlSession.insert(command.getName(), param));        break;      }      // 更新      case UPDATE: {        Object param = method.convertArgsToSqlCommandParam(args);        // sqlSession.insert(): 調(diào)用SqlSession執(zhí)行更新操作        // rowCountResult(): 獲取SQL語(yǔ)句的執(zhí)行結(jié)果        result = rowCountResult(sqlSession.update(command.getName(), param));        break;      }      // 刪除      case DELETE: {        Object param = method.convertArgsToSqlCommandParam(args);        // sqlSession.insert(): 調(diào)用SqlSession執(zhí)行更新操作        // rowCountResult(): 獲取SQL語(yǔ)句的執(zhí)行結(jié)果        result = rowCountResult(sqlSession.delete(command.getName(), param));        break;      }      // 查詢      case SELECT:        // method.returnsVoid(): 返參是否為void        // method.hasResultHandler(): 是否有對(duì)應(yīng)的結(jié)果處理器        if (method.returnsVoid() && method.hasResultHandler()) {          executeWithResultHandler(sqlSession, args);          result = null;        } else if (method.returnsMany()) { // 查詢多條記錄          result = executeForMany(sqlSession, args);        } else if (method.returnsMap()) { // 查詢結(jié)果返參為Map          result = executeForMap(sqlSession, args);        } else if (method.returnsCursor()) { // 以游標(biāo)的方式進(jìn)行查詢          result = executeForCursor(sqlSession, args);        } else {          // 參數(shù)轉(zhuǎn)換 轉(zhuǎn)成sqlCommand參數(shù)          Object param = method.convertArgsToSqlCommandParam(args);          // 執(zhí)行查詢 查詢單條數(shù)據(jù)          result = sqlSession.selectOne(command.getName(), param);          if (method.returnsOptional()              && (result == null || !method.getReturnType().equals(result.getClass()))) {            result = Optional.ofNullable(result);          }        }        break;      case FLUSH: // 執(zhí)行清除操作        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;  }

在上面,有多處出現(xiàn)這樣一行代碼:method.convertArgsToSqlCommandParam (args),該方法的作用就是將方法參數(shù)轉(zhuǎn)換為 SqlCommandParam;具體交由paramNameResolver.getNamedParams () 實(shí)現(xiàn)。在看 paramNameResolver.getNamedParams () 之前,我們先來(lái)看下 paramNameResolver 是什么東西?

public Object convertArgsToSqlCommandParam(Object[] args) {      return paramNameResolver.getNamedParams(args);    }

在前面,我們?cè)趯?shí)例化 MethodSignature 對(duì)象 (new MethodSignature) 的時(shí)候,在其構(gòu)造方法中,會(huì)實(shí)例化 ParamNameResolver 對(duì)象,該對(duì)象主要用來(lái)處理接口形式的參數(shù),最后會(huì)把參數(shù)處放在一個(gè) map(即屬性 names)中。map 的 key 為參數(shù)的位置,value 為參數(shù)的名字。

public MethodSignature(Configuration configuration, Class mapperInterface, Method method) {  ...  this.paramNameResolver = new ParamNameResolver(configuration, method);}

對(duì) names 字段的解釋?zhuān)?/strong>

假設(shè)在 xxxMapper 中有這么一個(gè)接口方法 selectByIdAndName ()

?selectByIdAndName (@Param ("id") String id, @Param ("name") String name) 轉(zhuǎn)化為 map 為 {{0, "id"}, {1, "name"}}

?selectByIdAndName (String id, String name) 轉(zhuǎn)化為 map 為 {{0, "0"}, {1, "1"}}

?selectByIdAndName (int a, RowBounds rb, int b) 轉(zhuǎn)化為 map 為 {{0, "0"}, {2, "1"}}

構(gòu)造方法的會(huì)經(jīng)歷如下的步驟

1. 通過(guò)反射得到方法的參數(shù)類(lèi)型和方法的參數(shù)注解注解,method.getParameterAnnotations () 方法返回的是注解的二維數(shù)組,每一個(gè)方法的參數(shù)包含一個(gè)注解數(shù)組。

2. 遍歷所有的參數(shù)

- 首先判斷這個(gè)參數(shù)的類(lèi)型是否是特殊類(lèi)型,RowBounds 和 ResultHandler,是的話跳過(guò),咱不處理

- 判斷這個(gè)參數(shù)是否是用來(lái) Param 注解,如果使用的話 name 就是 Param 注解的值,并把 name 放到 map 中,鍵為參數(shù)在方法中的位置,value 為 Param 的值

- 如果沒(méi)有使用 Param 注解,判斷是否開(kāi)啟了 UseActualParamName,如果開(kāi)啟了,則使用 java8 的反射得到方法的名字,此處容易造成異常,

具體原因參考上一篇博文.

- 如果以上條件都不滿足的話,則這個(gè)參數(shù)的名字為參數(shù)的下標(biāo)

// 通用key前綴,因?yàn)閗ey有param1,param2,param3等;  public static final String GENERIC_NAME_PREFIX = "param";  // 存放參數(shù)的位置和對(duì)應(yīng)的參數(shù)名  private final SortedMap names;  // 是否使用@Param注解  private boolean hasParamAnnotation;  public ParamNameResolver(Configuration config, Method method) {    // 通過(guò)注解得到方法的參數(shù)類(lèi)型數(shù)組    final Class[] paramTypes = method.getParameterTypes();    // 通過(guò)反射得到方法的參數(shù)注解數(shù)組    final Annotation[][] paramAnnotations = method.getParameterAnnotations();    // 用于存儲(chǔ)所有參數(shù)名的SortedMap對(duì)象    final SortedMap map = new TreeMap<>();    // 參數(shù)注解數(shù)組長(zhǎng)度,即方法入?yún)⒅杏袔讉€(gè)地方使用了@Param    // 如selectByIdAndName(@Param("id") String id, @Param("name") String name)中,paramCount=2    int paramCount = paramAnnotations.length;    // 遍歷所有的參數(shù)    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {      // 判斷這個(gè)參數(shù)的類(lèi)型是否是特殊類(lèi)型,RowBounds和ResultHandler,是的話跳過(guò)      if (isSpecialParameter(paramTypes[paramIndex])) {        continue;      }      String name = null;      for (Annotation annotation : paramAnnotations[paramIndex]) {        // 判斷這個(gè)參數(shù)是否使用了@Param注解        if (annotation instanceof Param) {          // 標(biāo)記當(dāng)前方法使用了Param注解          hasParamAnnotation = true;          // 如果使用的話name就是Param注解的值          name = ((Param) annotation).value();          break;        }      }      // 如果經(jīng)過(guò)上面處理,參數(shù)名還是null,則說(shuō)明當(dāng)前參數(shù)沒(méi)有指定@Param注解      if (name == null) {        // 判斷是否開(kāi)啟了UseActualParamName        if (config.isUseActualParamName()) {          // 如果開(kāi)啟了,則使用java8的反射得到該參數(shù)對(duì)應(yīng)的屬性名          name = getActualParamName(method, paramIndex);        }        // 如果name還是為null        if (name == null) {          // use the parameter index as the name ("0", "1", ...)          // 使用參數(shù)在map中的下標(biāo)作為參數(shù)的name,如 ("0", "1", ...)          name = String.valueOf(map.size());        }      }      // 把參數(shù)放入到map中,key為參數(shù)在方法中的位置,value為參數(shù)的name(@Param的value值/參數(shù)對(duì)應(yīng)的屬性名/參數(shù)在map中的位置下標(biāo))      map.put(paramIndex, name);    }    // 最后使用Collections工具類(lèi)的靜態(tài)方法將結(jié)果map變?yōu)橐粋€(gè)不可修改類(lèi)型    names = Collections.unmodifiableSortedMap(map);  }

getNamedParams():該方法會(huì)將參數(shù)名和參數(shù)值對(duì)應(yīng)起來(lái),并且還會(huì)額外保存一份以 param 開(kāi)頭加參數(shù)順序數(shù)字的值

public Object getNamedParams(Object[] args) {    // 這里的names就是ParamNameResolver中的names,在構(gòu)造ParamNameResolver對(duì)象的時(shí)候,創(chuàng)建了該Map    // 獲取方法參數(shù)個(gè)數(shù)    final int paramCount = names.size();    // 沒(méi)有參數(shù)    if (args == null || paramCount == 0) {      return null;    // 只有一個(gè)參數(shù),并且沒(méi)有使用@Param注解。    } else if (!hasParamAnnotation && paramCount == 1) {      // 直接返回,不做任務(wù)處理      return args[names.firstKey()];    } else {      // 包裝成ParamMap對(duì)象。這個(gè)對(duì)象繼承了HashMap,重寫(xiě)了get方法。      final Map param = new ParamMap<>();      int i = 0;      // 遍歷names中的所有鍵值對(duì)      for (Map.Entry entry : names.entrySet()) {        // 將參數(shù)名作為key, 對(duì)應(yīng)的參數(shù)值作為value,放入結(jié)果param對(duì)象中        param.put(entry.getValue(), args[entry.getKey()]);        // 用于添加通用的參數(shù)名稱(chēng),按順序命名(param1, param2, ...)        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);        // 確保不覆蓋以@Param 命名的參數(shù)        if (!names.containsValue(genericParamName)) {          param.put(genericParamName, args[entry.getKey()]);        }        i++;      }      return param;    }  }}

getNamedParams () 總結(jié):

1. 當(dāng)只有一個(gè)參數(shù)的時(shí)候,直接返回,不做任務(wù)處理;

2. 否則,存入 Map 中,鍵值對(duì)形式為:paramName=paramValue

?selectByIdAndName (@Param ("id") String id, @Param ("name") String name): 傳入的參數(shù)是 ["1", "張三"],最后解析出來(lái)的 map 為:{“id”:”1”,”“name”:” 張三”}

?selectByIdAndName (String id, @Param ("name") String name): 傳入的參數(shù)是 ["1", "張三"],最后解析出來(lái)的 map 為:{“param1”:”1”,”“name”:” 張三”}

假設(shè)執(zhí)行的 SQL 語(yǔ)句是 select 類(lèi)型,繼續(xù)往下看代碼

在 mapperMethod.execute (), 當(dāng)convertArgsToSqlCommandParam () 方法處理完方法參數(shù)后,假設(shè)我們此時(shí)調(diào)用的是查詢單條記錄,那么接下來(lái)會(huì)執(zhí)行 sqlSession.selectOne () 方法。

sqlSession.selectOne () 源碼分析:

sqlSession.selectOne () 也是調(diào)的 sqlSession.selectList () 方法,只不過(guò)只返回 list 中的第一條數(shù)據(jù)。當(dāng) list 中有多條數(shù)據(jù)時(shí),拋異常。

@Overridepublic  T selectOne(String statement, Object parameter) {  // 調(diào)用當(dāng)前類(lèi)的selectList方法  List list = this.selectList(statement, parameter);  if (list.size() == 1) {    return list.get(0);  } else if (list.size() > 1) {    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());  } else {    return null;  }}

sqlSession.selectList () 方法

@Override  public  List selectList(String statement, Object parameter) {    return this.selectList(statement, parameter, RowBounds.DEFAULT);  }

繼續(xù)看:

@Override  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {    try {      // 從Configuration里的mappedStatements里根據(jù)key(id的全路徑)獲取MappedStatement對(duì)象      MappedStatement ms = configuration.getMappedStatement(statement);      // 調(diào)用Executor的實(shí)現(xiàn)類(lèi)BaseExecutor的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();    }  }

在 sqlSession.selectList () 方法中,我們可以看到調(diào)用了 executor.query (),假設(shè)我們開(kāi)啟了二級(jí)緩存,那么 executor.query () 調(diào)用的是 executor 的實(shí)現(xiàn)類(lèi) CachingExecutor 中的 query (),二級(jí)緩存的邏輯就是在 CachingExecutor 這個(gè)類(lèi)中實(shí)現(xiàn)的。

關(guān)于 mybatis 二級(jí)緩存:

二級(jí)緩存默認(rèn)是不開(kāi)啟的,需要手動(dòng)開(kāi)啟二級(jí)緩存,實(shí)現(xiàn)二級(jí)緩存的時(shí)候,MyBatis 要求返回的 POJO 必須是可序列化的。緩存中存儲(chǔ)的是序列化之后的,所以不同的會(huì)話操作對(duì)象不會(huì)改變緩存。

怎么開(kāi)啟二級(jí)緩存:

    

怎么使用二級(jí)緩存?

1. 首先肯定是要開(kāi)啟二級(jí)緩存啦~

2. 除此之外,要使用二級(jí)緩存還要滿足以下條件:

?當(dāng)會(huì)話提交之后才會(huì)填充二級(jí)緩存(為什么?后面會(huì)解釋?zhuān)?/p>

?SQL 語(yǔ)句相同,參數(shù)相同

?相同的 statementID

?RowBounds 相同

為什么要會(huì)話提交后才會(huì)填充二級(jí)緩存?

首先,我們知道,與一級(jí)緩存(會(huì)話級(jí)緩存)不同的是,二級(jí)緩存是跨線程使用的,也就是多個(gè)會(huì)話可以一起使用同一個(gè)二級(jí)緩存。假設(shè)現(xiàn)在不用提交便可以填充二級(jí)緩存,我們看看會(huì)存在什么問(wèn)題?

假設(shè)會(huì)話二現(xiàn)在對(duì)數(shù)據(jù)庫(kù)進(jìn)行了修改操作,修改完進(jìn)行了查詢操縱,如果不用提交就會(huì)填充二級(jí)緩存的話,這時(shí)候查詢操作會(huì)把剛才修改的數(shù)據(jù)填充到二級(jí)緩存中,如果此時(shí)剛好會(huì)話一執(zhí)行了查詢操作,便會(huì)查詢到二級(jí)緩存中的數(shù)據(jù)。如果會(huì)話二最終回滾了剛才的修改操作,那么會(huì)話一就相當(dāng)于發(fā)生了臟讀。

實(shí)際上,查詢的時(shí)候會(huì)填充緩存,只不過(guò)此時(shí)是填充在暫存區(qū),而不是填充在真正的二級(jí)緩存區(qū)中。而上面所說(shuō)的要會(huì)話提交后才會(huì)填充二級(jí)緩存,指的是將暫存區(qū)中的緩存刷到真正的二級(jí)緩存中。啊???那不對(duì)呀,填充在暫存區(qū),那此時(shí)會(huì)話一來(lái)查詢,豈不是還會(huì)從暫存區(qū)中取到緩存,從而導(dǎo)致臟讀?別急,接著往下看。

對(duì)于查詢操作,每次取緩存都是從真正的二級(jí)緩存中取緩存,而不是從暫存區(qū)中取緩存。

好了,我們接著看源碼~

CachingExecutor.query () 源碼:

@Override  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {    // 獲取要執(zhí)行的sql語(yǔ)句 sql語(yǔ)句在解析xml的時(shí)候就已經(jīng)解析好了    BoundSql boundSql = ms.getBoundSql(parameterObject);    // 生成二級(jí)緩存key    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);    // 調(diào)用重載方法    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  }

調(diào)用重載方法:query ()

@Override  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)      throws SQLException {    // 獲取mybatis的二級(jí)緩存配置    Cache cache = ms.getCache();    // 如果配置了二級(jí)緩存    if (cache != null) {      // 是否要刷新緩存,是否手動(dòng)設(shè)置了需要清空緩存      flushCacheIfRequired(ms);      if (ms.isUseCache() && resultHandler == null) {        ensureNoOutParams(ms, boundSql);        @SuppressWarnings("unchecked")        // 從二級(jí)緩存中獲取值        List list = (List) tcm.getObject(cache, key);        // 從二級(jí)緩存中取不到值        if (list == null) {          // 交由delegate查詢 這里的delegate指向的是BaseExecutor          // BaseExecutor中實(shí)現(xiàn)了一級(jí)緩存的相關(guān)邏輯          // 也就是說(shuō),當(dāng)在二級(jí)緩存中獲取不到值的時(shí)候,會(huì)從一級(jí)緩存中獲取,一級(jí)緩存要是還是獲取不到          // 才會(huì)去查詢數(shù)據(jù)庫(kù)          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);          // 將查詢結(jié)果存放在暫存區(qū)中,只有會(huì)話提交后才會(huì)將數(shù)據(jù)刷到二級(jí)緩存,避免臟讀問(wèn)題          tcm.putObject(cache, key, list); // issue #578 and #116        }        return list;      }    }    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  }

接著,我們看下 BaseExecutor.query () 是怎么實(shí)現(xiàn)一級(jí)緩存邏輯的:

@SuppressWarnings("unchecked")  @Override  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++;      // 嘗試從緩存中獲取結(jié)果 一級(jí)緩存      list = resultHandler == null ? (List) localCache.getObject(key) : null;      if (list != null) {        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);      } else {// 從緩存中獲取不到結(jié)果時(shí)        // 從數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);      }    } finally {      queryStack--;    }    if (queryStack == 0) { // 回到主查詢      // 遍歷延遲加載中的數(shù)據(jù)      for (DeferredLoad deferredLoad : deferredLoads) {        // 把延遲加載的數(shù)據(jù)加載到結(jié)果集中        deferredLoad.load();      }      // issue #601      deferredLoads.clear();      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {        // issue #482        clearLocalCache();      }    }    return list;  }

當(dāng)從一級(jí)緩存中獲取不到數(shù)據(jù)時(shí),會(huì)查數(shù)據(jù)庫(kù):

調(diào)用BaseExecutor.queryFromDatabase()

private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {    List list;    // 占位符 (解決循環(huán)依賴問(wèn)題)    localCache.putObject(key, EXECUTION_PLACEHOLDER);    try {      // 執(zhí)行查詢操作      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);    } finally {      // 將占位符從緩存中移除      localCache.removeObject(key);    }    // 將查詢結(jié)果放入到一級(jí)緩存中    localCache.putObject(key, list);    if (ms.getStatementType() == StatementType.CALLABLE) {      localOutputParameterCache.putObject(key, parameter);    }    return list;  }

調(diào)用 BaseExecutor.doQuery ():在 BaseExecutor 中,doQuery () 只是個(gè)抽象方法,具體交由子類(lèi)實(shí)現(xiàn):

protected abstract  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)      throws SQLException;

從前面的流程中可知,在每次執(zhí)行 CURD 的時(shí)候,都需要獲取 SqlSession 這個(gè)對(duì)象,接口如下:

可以看出來(lái)這個(gè)接口主要定義類(lèi)關(guān)于 CRUD、數(shù)據(jù)庫(kù)事務(wù)、數(shù)據(jù)庫(kù)刷新等相關(guān)操作。下面看它的默認(rèn)實(shí)現(xiàn)類(lèi):

可以看到 DefaultSqlSession 實(shí)現(xiàn)了 SqlSession 中的方法,(其實(shí)我們自己也可根據(jù)需要去實(shí)現(xiàn))。而在 DefaultSqlSession 類(lèi)中有一個(gè)很重要的屬性,就是Mybatis 的執(zhí)行器(Executor)。

Executor 介紹:

Executor 執(zhí)行器,是 mybatis 中執(zhí)行查詢的主要代碼,Executor 分為三種:

?簡(jiǎn)單執(zhí)行器 SimpleExecutor

?可重用執(zhí)行器 ReuseExecutor

?批量執(zhí)行器 BatchExecutor

默認(rèn)使用的執(zhí)行器是SimpleExecutor,可以在 mybatis 的配置文件中設(shè)置使用哪種執(zhí)行器

public class Configuration {    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;}

Executor 類(lèi)圖:

?假設(shè)我們使用的就是默認(rèn)的執(zhí)行器,SimpleExecutor。我們來(lái)看下 SimpleExecutor.doQuery ()

@Override  public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {    // 這里就進(jìn)入jdbc了    Statement stmt = null;    try {      // 獲取核心配置對(duì)象      Configuration configuration = ms.getConfiguration();      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);      //預(yù)編譯SQL語(yǔ)句      stmt = prepareStatement(handler, ms.getStatementLog());      // 執(zhí)行查詢      return handler.query(stmt, resultHandler);    } finally {      closeStatement(stmt);    }  }
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {    Statement stmt;    // 獲取連接 這里的連接是代理連接    Connection connection = getConnection(statementLog);    // 預(yù)編譯    stmt = handler.prepare(connection, transaction.getTimeout());    // 給預(yù)編譯sql語(yǔ)句設(shè)置參數(shù)    handler.parameterize(stmt);    return stmt;  }

在上面的源碼中,我們可以看到 StatementHandler,它是用來(lái)干嘛的?

在 mybatis 中,通過(guò) StatementHandler 來(lái)處理與 JDBC 的交互,我們看下 StatementHandler 的類(lèi)圖:

可以看出,跟 Executor 的繼承實(shí)現(xiàn)很像,都有一個(gè) Base,Base 下面又有幾個(gè)具體實(shí)現(xiàn)子類(lèi),很明顯,采用了模板模式。不同于 CacheExecutor 用于二級(jí)緩存之類(lèi)的實(shí)際作用,這里的 RoutingStatementHandler 僅用于維護(hù)三個(gè) Base 子類(lèi)的創(chuàng)建與調(diào)用。

?BaseStatementHandler

?SimpleStatementHandler:JDBC 中的 Statement 接口,處理簡(jiǎn)單 SQL 的

?CallableStatementHandler:JDBC 中的 PreparedStatement,預(yù)編譯 SQL 的接口

?PreparedStatementHandler:JDBC 中的 CallableStatement,用于執(zhí)行存儲(chǔ)過(guò)程相關(guān)的接口

?RoutingStatementHandler:路由三個(gè) Base 子類(lèi),負(fù)責(zé)其創(chuàng)建及調(diào)用

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {    switch (ms.getStatementType()) {      // 策略模式:根據(jù)不同語(yǔ)句類(lèi)型 選用不同的策略實(shí)現(xiàn)類(lèi)      case STATEMENT:        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);        break;      case PREPARED:        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);        break;      case CALLABLE:        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);        break;      default:        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());    }  }

嗯,很眼熟的策略模式,按照 statementType 的值來(lái)決定返回哪種 StatementHandler。

那這里的statementType 是在哪里賦值的呢?我們看下MappedStatement 的構(gòu)造方法:

public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {      ...      // 構(gòu)造方法中默認(rèn)取值為PREPARED      mappedStatement.statementType = StatementType.PREPARED;      ...    }

如果不想使用的 StatementType.PREPARED,怎么自定義呢?

(1) 在 xxxMapper.xml 中:可以通過(guò) SELECT * FROM Student

(2) 如果采用的是注解開(kāi)發(fā):通過(guò) @SelectKey 的 statementType 屬性指定

@SelectKey(keyProperty = "account",         before = false,         statementType = StatementType.STATEMENT,         statement = "select * from account where id = #{id}",         resultType = Account.class)Account selectByPrimaryKey(@Param("id") Integer id);

到此,select 類(lèi)型的 SQL 語(yǔ)句就基本執(zhí)行完畢了,我們來(lái)總結(jié)一下 mybatis

MyBatis 的主要的核心部件有以下幾個(gè):

SqlSession:作為 MyBatis 工作的主要頂層 API,表示和數(shù)據(jù)庫(kù)交互的會(huì)話,完成必要數(shù)據(jù)庫(kù)增刪改查功能;

Executor:MyBatis 執(zhí)行器,是 MyBatis 調(diào)度的核心,負(fù)責(zé) SQL 語(yǔ)句的生成和查詢緩存的維護(hù);

StatementHandler:封裝了 JDBC Statement 操作,負(fù)責(zé)對(duì) JDBC statement 的操作,如設(shè)置參數(shù)、將 Statement 結(jié)果集轉(zhuǎn)換成 List 集合。

ParameterHandler:負(fù)責(zé)對(duì)用戶傳遞的參數(shù)轉(zhuǎn)換成 JDBC Statement 所需要的參數(shù);

ResultSetHandler:負(fù)責(zé)將 JDBC 返回的 ResultSet 結(jié)果集對(duì)象轉(zhuǎn)換成 List 類(lèi)型的集合;

TypeHandler:負(fù)責(zé) java 數(shù)據(jù)類(lèi)型和 jdbc 數(shù)據(jù)類(lèi)型之間的映射和轉(zhuǎn)換;

MappedStatement:MappedStatement 維護(hù)了一條 節(jié)點(diǎn)的封裝;

SqlSource:負(fù)責(zé)根據(jù)用戶傳遞的 parameterObject,動(dòng)態(tài)地生成 SQL 語(yǔ)句,將信息封裝到 BoundSql 對(duì)象中,并返回;

BoundSql:表示動(dòng)態(tài)生成的 SQL 語(yǔ)句以及相應(yīng)的參數(shù)信息;

Configuration:MyBatis 所有的配置信息都維持在 Configuration 對(duì)象之中;

標(biāo)簽: 二級(jí)緩存 一級(jí)緩存 構(gòu)造方法

上一篇:世界速訊:狂神說(shuō) 網(wǎng)絡(luò)編程實(shí)戰(zhàn)講解
下一篇:快訊:小樂(lè)樂(lè)改數(shù)字題