剛開始使用Mybaits的同學(xué)有沒有這樣的疑惑,為什么我們沒有編寫Mapper的實(shí)現(xiàn)類,卻能調(diào)用Mapper的方法呢?本篇文章我?guī)Т蠹乙黄饋斫鉀Q這個(gè)疑問

          上一篇文章我們獲取到了DefaultSqlSession,接著我們來看第一篇文章測(cè)試用例后面的代碼
          EmployeeMapper employeeMapper = sqlSession.getMapper(Employee.class); List
          <Employee> allEmployees = employeeMapper.getAll();
          為 Mapper 接口創(chuàng)建代理對(duì)象

          我們先從 DefaultSqlSession 的 getMapper 方法開始看起,如下:
          1 public <T> T getMapper(Class<T> type) { 2 return
          configuration.<T>getMapper(type,this); 3 } 4 5 // Configuration 6 public
          <T> T getMapper(Class<T> type, SqlSession sqlSession) { 7 return
          mapperRegistry.getMapper(type, sqlSession); 8 } 9 10 // MapperRegistry 11
          public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 12 // 從
          knownMappers 中獲取與 type 對(duì)應(yīng)的 MapperProxyFactory 13 final MapperProxyFactory<T>
          mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 14 if
          (mapperProxyFactory ==null) { 15 throw new BindingException("Type " + type + "
          is not known to the MapperRegistry."); 16 } 17 try { 18 // 創(chuàng)建代理對(duì)象 19 return
          mapperProxyFactory.newInstance(sqlSession);20 } catch (Exception e) { 21 throw
          new BindingException("Error getting mapper instance. Cause: " + e, e); 22 } 23
          }
          這里最重要就是兩行代碼,第13行和第19行,我們接下來就分析這兩行代碼

          獲取MapperProxyFactory

          根據(jù)名稱看,可以理解為Mapper代理的創(chuàng)建工廠,是不是Mapper的代理對(duì)象由它創(chuàng)建呢?我們先來回顧一下knownMappers
          集合中的元素是何時(shí)存入的。這要在我前面的文章中找答案,MyBatis 在解析配置文件的 <mappers> 節(jié)點(diǎn)的過程中,會(huì)調(diào)用 MapperRegistry
          的 addMapper 方法將 Class 到 MapperProxyFactory 對(duì)象的映射關(guān)系存入到
          knownMappers。有興趣的同學(xué)可以看看我之前的文章,我們來回顧一下源碼:
          private void bindMapperForNamespace() { // 獲取映射文件的命名空間 String namespace =
          builderAssistant.getCurrentNamespace();if (namespace != null) { Class<?>
          boundType =null; try { // 根據(jù)命名空間解析 mapper 類型 boundType =
          Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if
          (boundType !=null) { // 檢測(cè)當(dāng)前 mapper 類是否被綁定過 if (!
          configuration.hasMapper(boundType)) { configuration.addLoadedResource(
          "namespace:" + namespace); // 綁定 mapper 類 configuration.addMapper(boundType);
          } } } }// Configuration public <T> void addMapper(Class<T> type) { // 通過
          MapperRegistry 綁定 mapper 類 mapperRegistry.addMapper(type); } // MapperRegistry
          public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if
          (hasMapper(type)) {throw new BindingException("Type " + type + " is already
          known to the MapperRegistry."); } boolean loadCompleted = false; try { /* * 將
          type 和 MapperProxyFactory 進(jìn)行綁定,MapperProxyFactory 可為 mapper 接口生成代理類*/
          knownMappers.put(type,new MapperProxyFactory<T>(type)); MapperAnnotationBuilder
          parser= new MapperAnnotationBuilder(config, type); // 解析注解中的信息 parser.parse();
          loadCompleted= true; } finally { if (!loadCompleted) {
          knownMappers.remove(type); } } } }
          在解析Mapper.xml的最后階段,獲取到Mapper.xml的namespace,然后利用反射,獲取到namespace的Class,并創(chuàng)建一個(gè)
          MapperProxyFactory的實(shí)例,namespace的Class作為參數(shù),最后將namespace的Class為key,
          MapperProxyFactory的實(shí)例為value存入knownMappers。

          注意,我們這里是通過映射文件的命名空間的Class當(dāng)做knownMappers的Key。然后我們看看
          getMapper方法的13行,是通過參數(shù)Employee.class也就是Mapper接口的Class來獲取
          MapperProxyFactory,所以我們明白了為什么要求xml配置中的namespace要和和對(duì)應(yīng)的Mapper接口的全限定名了

          生成代理對(duì)象

          我們看第19行代碼?return mapperProxyFactory.newInstance(sqlSession);,
          很明顯是調(diào)用了MapperProxyFactory的一個(gè)工廠方法,我們跟進(jìn)去看看
          public class MapperProxyFactory<T> { //存放Mapper接口Class private final Class<T>
          mapperInterface;private final Map<Method, MapperMethod> methodCache = new
          ConcurrentHashMap();public MapperProxyFactory(Class<T> mapperInterface) { this
          .mapperInterface = mapperInterface; } public Class<T> getMapperInterface() {
          return this.mapperInterface; } public Map<Method, MapperMethod>
          getMethodCache() {return this.methodCache; } protected T
          newInstance(MapperProxy<T> mapperProxy) { //生成mapperInterface的代理類 return
          Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this
          .mapperInterface}, mapperProxy); }public T newInstance(SqlSession sqlSession) {
          /* * 創(chuàng)建 MapperProxy 對(duì)象,MapperProxy 實(shí)現(xiàn)了 InvocationHandler 接口,代理邏輯封裝在此類中 *
          將sqlSession傳入MapperProxy對(duì)象中,第二個(gè)參數(shù)是Mapper的接口,并不是其實(shí)現(xiàn)類*/ MapperProxy<T>
          mapperProxy =new MapperProxy(sqlSession, this.mapperInterface, this
          .methodCache);return this.newInstance(mapperProxy); } }
          上面的代碼首先創(chuàng)建了一個(gè) MapperProxy 對(duì)象,該對(duì)象實(shí)現(xiàn)了 InvocationHandler
          接口。然后將對(duì)象作為參數(shù)傳給重載方法,并在重載方法中調(diào)用 JDK 動(dòng)態(tài)代理接口為 Mapper接口 生成代理對(duì)象。

          這里要注意一點(diǎn),MapperProxy這個(gè)InvocationHandler
          創(chuàng)建的時(shí)候,傳入的參數(shù)并不是Mapper接口的實(shí)現(xiàn)類,我們以前是怎么創(chuàng)建JDK動(dòng)態(tài)代理的?先創(chuàng)建一個(gè)接口,然后再創(chuàng)建一個(gè)接口的實(shí)現(xiàn)類,最后創(chuàng)建一個(gè)InvocationHandler并將實(shí)現(xiàn)類傳入其中作為目標(biāo)類,創(chuàng)建接口的代理類,然后調(diào)用代理類方法時(shí)會(huì)回調(diào)InvocationHandler的invoke方法,最后在invoke方法中調(diào)用目標(biāo)類的方法,但是我們這里調(diào)用Mapper接口代理類的方法時(shí),需要調(diào)用其實(shí)現(xiàn)類的方法嗎?不需要,我們需要調(diào)用對(duì)應(yīng)的配置文件的SQL,所以這里并不需要傳入Mapper的實(shí)現(xiàn)類到MapperProxy中,那Mapper接口的代理對(duì)象是如何調(diào)用對(duì)應(yīng)配置文件的SQL呢?下面我們來看看。

          Mapper代理類如何執(zhí)行SQL?


          上面一節(jié)中我們已經(jīng)獲取到了EmployeeMapper的代理類,并且其InvocationHandler為MapperProxy,那我們接著看Mapper接口方法的調(diào)用
          List<Employee> allEmployees = employeeMapper.getAll();

          知道JDK動(dòng)態(tài)代理的同學(xué)都知道,調(diào)用代理類的方法,最后都會(huì)回調(diào)到InvocationHandler的Invoke方法,那我們來看看這個(gè)InvocationHandler(MapperProxy)
          public class MapperProxy<T> implements InvocationHandler, Serializable {
          private final SqlSession sqlSession; private final Class<T> mapperInterface;
          private final Map<Method, MapperMethod> methodCache; public
          MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method,
          MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface
          = mapperInterface; this.methodCache = methodCache; } public Object invoke
          (Object proxy, Method method, Object[] args)throws Throwable { // 如果方法是定義在
          Object 類中的,則直接調(diào)用 if (Object.class.equals(method.getDeclaringClass())) { try {
          return method.invoke(this, args); } catch (Throwable var5) { throw
          ExceptionUtil.unwrapThrowable(var5); } }else { // 從緩存中獲取 MapperMethod
          對(duì)象,若緩存未命中,則創(chuàng)建 MapperMethod 對(duì)象 MapperMethod mapperMethod = this
          .cachedMapperMethod(method);// 調(diào)用 execute 方法執(zhí)行 SQL return mapperMethod.execute(
          this.sqlSession, args); } } private MapperMethod cachedMapperMethod(Method
          method) { MapperMethod mapperMethod= (MapperMethod)this.methodCache.get(method);
          if (mapperMethod == null) { //
          創(chuàng)建一個(gè)MapperMethod,參數(shù)為mapperInterface和method還有Configuration mapperMethod = new
          MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
          this.methodCache.put(method, mapperMethod); } return mapperMethod; } }
          如上,回調(diào)函數(shù)invoke邏輯會(huì)首先檢測(cè)被攔截的方法是不是定義在 Object 中的,比如 equals、hashCode
          方法等。對(duì)于這類方法,直接執(zhí)行即可。緊接著從緩存中獲取或者創(chuàng)建 MapperMethod 對(duì)象,然后通過該對(duì)象中的 execute 方法執(zhí)行
          SQL。我們先來看看如何創(chuàng)建MapperMethod

          創(chuàng)建 MapperMethod 對(duì)象
          public class MapperMethod { //
          包含SQL相關(guān)信息,比喻MappedStatement的id屬性,(mapper.EmployeeMapper.getAll) private final
          SqlCommand command;//包含了關(guān)于執(zhí)行的Mapper方法的參數(shù)類型和返回類型。 private final MethodSignature
          method;public MapperMethod(Class<?> mapperInterface, Method method,
          Configuration config) {// 創(chuàng)建 SqlCommand 對(duì)象,該對(duì)象包含一些和 SQL 相關(guān)的信息 this.command = new
          SqlCommand(config, mapperInterface, method);// 創(chuàng)建 MethodSignature
          對(duì)象,從類名中可知,該對(duì)象包含了被攔截方法的一些信息 this.method = new MethodSignature(config,
          mapperInterface, method); } }
          MapperMethod包含SqlCommand 和MethodSignature 對(duì)象,我們來看看其創(chuàng)建過程

          ① 創(chuàng)建 SqlCommand 對(duì)象
          public static class SqlCommand { //
          name為MappedStatement的id,也就是namespace.methodName(mapper.EmployeeMapper.getAll)
          private final String name; //SQL的類型,如insert,delete,update private final
          SqlCommandType type;public SqlCommand(Configuration configuration, Class<?>
          mapperInterface, Method method) {//
          拼接Mapper接口名和方法名,(mapper.EmployeeMapper.getAll) String statementName =
          mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null;
          //檢測(cè)configuration是否有key為mapper.EmployeeMapper.getAll的MappedStatement if
          (configuration.hasStatement(statementName)) {//獲取MappedStatement ms =
          configuration.getMappedStatement(statementName); } else if (!
          mapperInterface.equals(method.getDeclaringClass())) { String parentStatementName
          = method.getDeclaringClass().getName() + "." + method.getName(); if
          (configuration.hasStatement(parentStatementName)) { ms=
          configuration.getMappedStatement(parentStatementName); } }// 檢測(cè)當(dāng)前方法是否有對(duì)應(yīng)的
          MappedStatement 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 { // 設(shè)置 name 和 type 變量
          name = ms.getId(); type = ms.getSqlCommandType(); if (type ==
          SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method
          for: " + name); } } } } public boolean hasStatement(String statementName,
          boolean validateIncompleteStatements) { //
          檢測(cè)configuration是否有key為statementName的MappedStatement return this
          .mappedStatements.containsKey(statementName); }
          通過拼接接口名和方法名,在configuration獲取對(duì)應(yīng)的MappedStatement,并設(shè)置設(shè)置 name 和 type 變量,代碼很簡(jiǎn)單

          ② 創(chuàng)建 MethodSignature 對(duì)象

          MethodSignature?包含了被攔截方法的一些信息,如目標(biāo)方法的返回類型,目標(biāo)方法的參數(shù)列表信息等。下面,我們來看一下
          MethodSignature 的構(gòu)造方法。
          public static class MethodSignature { private final boolean returnsMany;
          private final boolean returnsMap; private final boolean returnsVoid; private
          final boolean returnsCursor; private final Class<?> returnType; private final
          String mapKey;private final Integer resultHandlerIndex; private final Integer
          rowBoundsIndex;private final ParamNameResolver paramNameResolver; public
          MethodSignature(Configuration configuration, Class<?> mapperInterface, Method
          method) {// 通過反射解析方法返回類型 Type resolvedReturnType =
          TypeParameterResolver.resolveReturnType(method, mapperInterface);if
          (resolvedReturnTypeinstanceof Class<?>) { this.returnType = (Class<?>)
          resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
          this.returnType = (Class<?>) ((ParameterizedType)
          resolvedReturnType).getRawType(); }else { this.returnType =
          method.getReturnType(); }// 檢測(cè)返回值類型是否是 void、集合或數(shù)組、Cursor、Map 等 this.returnsVoid
          =void.class.equals(this.returnType); this.returnsMany =
          configuration.getObjectFactory().isCollection(this.returnType) || this
          .returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);
          // 解析 @MapKey 注解,獲取注解內(nèi)容 this.mapKey = getMapKey(method); this.returnsMap = this
          .mapKey !=null; /* * 獲取 RowBounds 參數(shù)在參數(shù)列表中的位置,如果參數(shù)列表中 * 包含多個(gè) RowBounds
          參數(shù),此方法會(huì)拋出異常*/ this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class
          );// 獲取 ResultHandler 參數(shù)在參數(shù)列表中的位置 this.resultHandlerIndex =
          getUniqueParamIndex(method, ResultHandler.class); // 解析參數(shù)列表 this
          .paramNameResolver =new ParamNameResolver(configuration, method); } }
          執(zhí)行 execute 方法

          前面已經(jīng)分析了 MapperMethod 的初始化過程,現(xiàn)在 MapperMethod 創(chuàng)建好了。那么,接下來要做的事情是調(diào)用 MapperMethod 的
          execute 方法,執(zhí)行 SQL。傳遞參數(shù)sqlSession和method的運(yùn)行參數(shù)args
          return mapperMethod.execute(this.sqlSession, args);
          我們?nèi)apperMethod 的execute方法中看看

          MapperMethod
          public Object execute(SqlSession sqlSession, Object[] args) { Object result; //
          根據(jù) SQL 類型執(zhí)行相應(yīng)的數(shù)據(jù)庫操作 switch (command.getType()) { case INSERT: { //
          對(duì)用戶傳入的參數(shù)進(jìn)行轉(zhuǎn)換,下同 Object param = method.convertArgsToSqlCommandParam(args); //
          執(zhí)行插入操作,rowCountResult 方法用于處理返回值 result =
          rowCountResult(sqlSession.insert(command.getName(), param));break; } case UPDATE
          : { Object param= method.convertArgsToSqlCommandParam(args); // 執(zhí)行更新操作 result =
          rowCountResult(sqlSession.update(command.getName(), param));break; } case DELETE
          : { Object param= method.convertArgsToSqlCommandParam(args); // 執(zhí)行刪除操作 result =
          rowCountResult(sqlSession.delete(command.getName(), param));break; } case SELECT
          :// 根據(jù)目標(biāo)方法的返回類型進(jìn)行相應(yīng)的查詢操作 if (method.returnsVoid() && method.hasResultHandler())
          { executeWithResultHandler(sqlSession, args); result= null; } else if
          (method.returnsMany()) {// 執(zhí)行查詢操作,并返回多個(gè)結(jié)果 result = executeForMany(sqlSession,
          args); }else if (method.returnsMap()) { // 執(zhí)行查詢操作,并將結(jié)果封裝在 Map 中返回 result =
          executeForMap(sqlSession, args); }else if (method.returnsCursor()) { //
          執(zhí)行查詢操作,并返回一個(gè) Cursor 對(duì)象 result = executeForCursor(sqlSession, args); } else {
          Object param= method.convertArgsToSqlCommandParam(args); // 執(zhí)行查詢操作,并返回一個(gè)結(jié)果
          result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: //
          執(zhí)行刷新操作 result = sqlSession.flushStatements(); break; default: throw new
          BindingException("Unknown execution method for: " + command.getName()); } return
          result; }
          如上,execute 方法主要由一個(gè) switch 語句組成,用于根據(jù) SQL
          類型執(zhí)行相應(yīng)的數(shù)據(jù)庫操作。我們先來看看是參數(shù)的處理方法convertArgsToSqlCommandParam是如何將方法參數(shù)數(shù)組轉(zhuǎn)化成Map的
          public Object convertArgsToSqlCommandParam(Object[] args) { return
          paramNameResolver.getNamedParams(args); }public Object getNamedParams(Object[]
          args) {final int paramCount = names.size(); if (args == null || paramCount == 0
          ) {return null; } else if (!hasParamAnnotation && paramCount == 1) { return
          args[names.firstKey()]; }else { //創(chuàng)建一個(gè)Map,key為method的參數(shù)名,值為method的運(yùn)行時(shí)參數(shù)值 final
          Map<String, Object> param =new ParamMap<Object>(); int i = 0; for
          (Map.Entry<Integer, String> entry : names.entrySet()) { // 添加 <參數(shù)名, 參數(shù)值> 鍵值對(duì)到
          param 中 param.put(entry.getValue(), args[entry.getKey()]); final String
          genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1); if (!
          names.containsValue(genericParamName)) { param.put(genericParamName,
          args[entry.getKey()]); } i++; } return param; } }
          我們看到,將Object[] args轉(zhuǎn)化成了一個(gè)Map<參數(shù)名, 參數(shù)值> ,接著我們就可以看查詢過程分析了,如下
          // 執(zhí)行查詢操作,并返回一個(gè)結(jié)果 result = sqlSession.selectOne(command.getName(), param);
          我們看到是通過sqlSession來執(zhí)行查詢的,并且傳入的參數(shù)為command.getName()和param,也就是
          namespace.methodName(mapper.EmployeeMapper.getAll)和方法的運(yùn)行參數(shù)。

          查詢操作我們下一篇文章單獨(dú)來講

          ?

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                二区中文字幕 | 亚洲电影欧美片日韩 | 俺去俺来也在线www色 | 91人妻日韩人妻无码专区精品 | 日韩欧美黄色网址 |