1. 程式人生 > >Mybatis面向介面程式設計

Mybatis面向介面程式設計

    在使用Mybatis的時候,我們通過sqlSession的各種方法和資料互動,比如查詢我們是通過sqlSession.selectList("Namespace.sqlId",paramObj),對於插入資料以及修改和刪除資料也是同樣的通過sqlSession的方法操作,傳入配置檔案中sql語句對應的唯一id以及動態拼裝sql的引數。然後返回的結果是泛型型別,也就是任意的Object型別。距離來說就是下面這條java程式碼:Object result = sqlSession.selsectOne("Namespace.sqlId",paramObj);但是這樣寫java程式碼其實是有隱患的,具體共有三個隱患:
第一是String型別的"namespace.sqlId"容易出錯,在Java程式碼中或這Mybatis對映檔案需要完全一致。不僅要namespace相同,還要sql的id也完全相同。
第二是請求引數是任意Object型別,可以是java基本型別,也可以是JavaBean型別,但是在Mybatis對映檔案中接收的parameterType的型別是確定的,如果兩個不型別不一致勢必會導致出錯。 第三是返回型別為泛型的任意型別,其實返回的型別也是在Mybatis對映檔案中通過resultMap或者resultType限定,如果我們在java程式碼中接收返回的資料型別和對映檔案中不同,肯定也會出問題。 第一部分:實現面向介面程式設計

那麼如何避免上面說的三個隱患呢 ?我們可以使用Mybatis提供的面向介面程式設計,具體的操作方法如下: 第一:編寫一個介面,(IUser.java) 介面暫時為空介面,介面檔案包路徑為:com.gusi.demo.idao.IUser 第二:修改對映檔案,(User.xml) 將namespace屬性值改為上面定義介面的類的全名稱:com.gusi.demo.idao.IUser。然後將每個sql語句的id記錄下來,接收引數型別記錄下來,以及返回型別記錄下來。
<mapper namespace="com.gusi.demo.idao.IUser">

  <resultMap type="com.gusi.demo.pojo.User" id="UserResult">
    <id column="id" jdbcType="INTEGER" property="id"/>
    <result column="username" jdbcType="VARCHAR" property="username"/>
    <result column="password" jdbcType="VARCHAR" property="password.encrypted"/>
    <result column="administrator" jdbcType="BOOLEAN" property="administrator"/>
  </resultMap>

  <select id="find" parameterType="long" resultMap="UserResult">
    SELECT * FROM user WHERE id = #{id:INTEGER}
  </select>
</mapper>
第三:給上面的每一個sql語句在介面類IUser.java中新增一個介面方法(上面只有一條sql語句,所以只新增一個介面方法) 介面方法的返回型別就為上面記錄的返回型別:com.gusi.demo.pojo.User型別,當然這個地方也支援java基本型別和String型別 介面方法的名稱就為上面記錄sql語句的id:find,這個id在同一個namespace下是唯一的 介面方法的請求引數就為上面記錄的引數型別:long,當然這個地方是支援JavaBean型別的引數型別
package com.gusi.demo.idao;
public interface IUser{
    public com.gusi.demo.pojo.User find(long id);//這就是對應的介面方法之一
}
第四:修改UserDao中對資料庫訪問的方法
SqlSession sqlSession = sqlSessionFactory.getSqlSession();//獲得一個sqlSession
//以前程式碼寫法如下:
//User user = sqlSession.selectOne("User.find",1L);
//改為面向介面程式設計:
IUser iUser = sqlSession.getMapper(IUser.class);//通過sqlSession獲取對應註冊介面
User user = iUser.find(1L);//直接調運介面方法就可以獲得對應的User物件
第五:測試介面
@Test
public void testFind(){
    User user = UserDao.find(1L);//其實是測試IUser.find(long id)方法
}
第二部分:面向介面程式設計原理簡單剖析     通過上面的步驟我們很容易就實現的面向介面程式設計,我們不僅規避了上面提到的幾種隱患,同時還使我們的程式碼更統一,便於管理。但是問題又來了,我們絕對沒有給那個介面寫任何實現類,怎麼掉介面的方法就能成功執行到指定的sql語句然後返回合理的結果了。Mybatis到底是怎麼實現的呢?要知道這個問題,只能通過看原始碼咯。在看原始碼之前,我們首先得了解java泛型以及java的動態代理,java泛型可參考:http://blog.csdn.net/dyy_gusi/article/details/46414721java動態代理可參考:http://blog.csdn.net/dyy_gusi/article/details/46414605第一步:獲取介面物件的代理物件 IUser iUser = sqlSession.getMapper(Iuser.class);這句程式碼其實是去獲取一個IUser介面物件的代理物件。 原始碼片段1:
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
 //獲得代理物件的工廠類
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
 //去獲得代理物件,調運原始碼片段2
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
原始碼片段2:
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
 //獲得了一個正真的介面物件的代理物件(java動態代理物件),就是IUser介面物件
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
原始碼片段3:
  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
  //在讀取配置檔案的過程中,將namespace對應的介面類(IUser.java)加入的map中,調運原始碼片段4
          configuration.addMapper(boundType);
        }
      }
    }
  }
原始碼片段4:
  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 {
  //根據介面類構建一個介面類代理物件放入到map中,以便原始碼片段1中獲取
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
整個過程梳理就是:在構建sqlSession的時候,會讀取配置檔案,配置檔案中包含對映檔案,所以先將對映檔案namespace對應的介面物件解析出來,然後會提前將每一個對映檔案對應的介面代理工廠物件(namespace對應的介面物件的代理工廠物件)加入到一個map中,然後sqlSession.getMapper方法會在map中獲得對應的介面的代理工廠,最終通過相應的工廠獲得相應的介面的代理物件(IUser物件)。 第二步:執行代理物件的invoke方法 iUser.find(1L);這句程式碼其實是通過獲得代理物件調運代理物件的invoke方法。 原始碼片段5:
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {//這段if內的程式碼是不會執行的,因為介面沒有實現類,所以不能調運實現類的方法。
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  //下面兩句才是真真有效的
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);//去執行對應的sql語句
  }
  //該方法會將原始碼片段6中物件的command屬性和method屬性賦值以便下一步執行sql語句
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
原始碼片段6:
public class MapperMethod {

  private final SqlCommand command;//sql的命令存在該物件中,包含sqlId和sql語句的型別是增刪改查哪種
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);//通過介面類和方法名字,可以得到namespace.sqlId的值
    this.method = new MethodSignature(config, method);
  }
 //執行sql語句
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
 //根據sqlCommand選擇執行哪種sql語句
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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 {
        Object param = method.convertArgsToSqlCommandParam(args);
 //如果是查詢,就調運sqlSession的真真的查詢方法
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      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;
  }
}
在invoke方法中通過反射獲得對映檔案中的對應namespace對應的sqlId,然後在執行配置的sql語句完成查詢操作。因為整個過程中,介面物件沒有實現類,所以在代理中其實是不會執行介面實現類的介面方法,而是巧妙的通過各種反射得到namespace.sqlId以及請求引數,執行真真的和資料庫互動相關sqlSession.selectOne(),sqlSession.selectList(),sqlSession.insert()等各種方法。所以我們看似介面沒有實現類,但是調運介面方法卻完成了資料庫的互動操作,這都是Mybatis幫助我們完成的任務。

相關推薦

mybatis面向介面程式設計實踐與mapper代理物件的產生

上篇提到面向介面程式設計需要使用getMapper獲取代理物件,今天我們來看一下mapper代理物件是如何產生的,在此之前先回憶下面試介面程式設計。 一、Mybatis面向介面程式設計實踐 (一)使用xml的方式 1.定義mapper介面 Mapper介面就是一

Mybatis面向介面程式設計

    在使用Mybatis的時候,我們通過sqlSession的各種方法和資料互動,比如查詢我們是通過sqlSession.selectList("Namespace.sqlId",paramObj),對於插入資料以及修改和刪除資料也是同樣的通過sqlSession的方法

mybatis面向介面程式設計,namespace屬性的值對應關係

背景:在Mybatis的專案中,因為使用傳統的Dao開發方式可以實現所需功能,但是採用這種方式在實現類會出現大量的重複程式碼(缺點一),在方法中也需要指定對映檔案執行語句的id,並且不能保證編寫時id的正確性(在多人開發中還可能出現id重名。已經寫實現類的時候會出現引數錯誤。

面向介面程式設計 待整理

Dependency and Coupling   抽象層是規則的制定者   面向抽象機制程式設計;   抽象機制是規則的制定者; 介面代表可變的參與者。   面向介面程式設計的核心是:分離與可變。     https://b

面向介面程式設計 學習 待整理

面向介面: 契約與履行; 一致性;   機制複用;程式碼複用;     在spring的jdbc程式設計中,在引入資料庫連線池的部分就做到了面向介面程式設計,以後無論換什麼資料庫連線池都不用修改程式碼 直接修改配置檔案即可。可以遮蔽到大量的底層操作,這種程式設計方

面向介面程式設計詳解-Java篇

 相信看到這篇文字的人已經不需要了解什麼是介面了,我就不再過多的做介紹了,直接步入正題,介面測試如何編寫。那麼在這一篇裡,我們用一個例子,讓各位對這個重要的程式設計思想有個直觀的印象。為充分考慮到初學者,所以這個例子非常簡單,望各位高手見諒。   為了擺脫新手的概念,我這裡也儘量不用main

PHP面向介面程式設計瞭解下(二)

通過上篇文章呢,我們對於這個面向介面程式設計有了一個基本的認知,完事這次,我們主要是來簡單延伸下我們的思維模式。 我們知道,PHP是弱型別語言,而且呼叫比較靈活,所以本人並不推薦大規模使用介面,可以在部分‘核心’程式碼中使用介面。 並且呢,因為PHP中的介面已經失去了很多介面本應該具有的

PHP面向介面程式設計瞭解下(一)

咱這裡先宣告下,面向介面程式設計並不是一種新的程式設計正規化,咱這裡說的是狹義的介面,也就是interface關鍵字。之後,咱們就先來看下介面的作用。 介面,主要是定義一套規範,來描述一個“物”的功能,要求如果現實中的“物”想成為可用的,就必須實現某些基本功能。可以這麼理解:對於實現介面的所

C語言面向物件程式設計面向介面程式設計(4)

 Java 中有 interface 關鍵字,C++ 中有抽象類或純虛類可以與 interface 比擬,C 語言中也可以實現類似的特性。     在面試 Java 程式設計師時我經常問的一個問題是:介面和抽象類有什麼區別。  &n

我理解的面向介面程式設計

從題外話說起,在古代沒有貨幣的時候,人們只能用某一樣東西去換取自己需要的另一樣東西。比如,張三需要一匹布,李四需要一頭鵝,正巧張三有一頭鵝,李四有一匹布,於是他們達成了共識,拿布與鵝進行交換,各取所需。但這種交易有很大的弊端,那就是交換物的不確定性,李四想要的是

Java面向介面程式設計,低耦合高內聚的設計哲學

介面體現的是一種規範和實現分離的設計哲學,充分利用介面可以極大的降低程式中各個模組之間的耦合,提高系統的可維護性以及可擴充套件性。     因此,很多的軟體架構設計理念都倡導“面向介面程式設計”而不是面向實現類程式設計,以期通過這種方式來降低程式的耦合。  

spring cloud 入門(六)【容錯機制二(通過方法容錯),這個方法是面向介面程式設計,我覺得更好一些】

程式碼結構如下:   pom 檔案中新增  hystrix <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>

Java之“面向介面程式設計”-2

補充(2018-12-09): 其實介面的另外一個概念就是監聽,比如我要從一個地方獲取某個引數的狀態,其實有很多種方法例如:事件匯流排、廣播、介面監聽等。而在這些方法中使用介面無疑是代價(程式碼量以及過程中消耗的時間)最小的。 還是使用程式碼演示吧: 思路:建立一個單例模式的類,任何呼叫

Spring結合JPA設計通用的BaseDao實現完全的面向介面程式設計

 這是我們團隊小專案中設計的通用BaseDao: public interface BaseDao<T> { /** * 新增實體類 * @param t * @return */ public T ad

面向介面程式設計和麵向物件程式設計的區別

我想,對於各位使用面向物件程式語言的程式設計師來說,“介面”這個名詞一定不陌生,但是不知各位有沒有這樣的疑惑:介面有什麼用途?它和抽象類有什麼區別?能不能用抽象類代替介面呢?而且,作為程式設計師,一定經常聽到“面向介面程式設計”這個短語,那麼它是什麼意思?有什麼思想內涵?

java web之面向介面程式設計

1.在類中呼叫介面的方法,而不關心具體的實現,有利於程式碼的解耦,有更好地可移植性和可擴充套件性!!!!! . //2.具體的方法流程 1配置servlet---2.構建Servlet的init()

面向介面程式設計的好處

面向介面程式設計就是面向抽象程式設計、面向規範程式設計,它帶來的最大的好處便是解耦、增強擴充套件性、遮蔽變化 舉例:非單例的情況,側重於強調[擴充套件性強] //命令介面 public interface ICommand{ void doCommand(); }

C#進階系列——MEF實現設計上的“鬆耦合”(終結篇:面向介面程式設計

序:忙碌多事的八月帶著些許的倦意早已步入尾聲,金秋九月承載著抗戰勝利70週年的喜慶撲面而來。沒來得及任何準備,似乎也不需要任何準備,因為生活不需要太多將來時。每天忙著上班、加班、白加班,忘了去憤,忘了去算計所謂的價值。天津爆炸事故時刻警示著我們生命的無常,逝者安息,活著的人生活還得繼續,珍惜生命,遠離傷害。武

android面向介面程式設計(抽象工廠模式,擴充套件性超強,Demo優化)

本分開始之前。咱先提出來幾個疑問: 介面有什麼用途? 面向介面程式設計的好處? 它和抽象類有什麼區別? 能不能用抽象類代替介面呢? 它和麵向物件程式設計是什麼關係? 本分主要分為: 1.面向介面程式設計和麵向物件程式設計是什麼關係? 2.介面

面向介面程式設計的好處分析

Java本身也是一個不斷完善的語言,他也在頻繁的改動他的系統API來完善,他的API是一個龐大的體系,互相關聯,如果不採用介面,而都是用實現類的話,那麼API的改動就會給整個體系帶來不穩定。而且如果改動API,那麼就會有大量採用舊API的專案因無法正常執行,會損失大量客戶。換句話說,JDK已經發布的API是一