最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Mybatis Mapper接口工作原理實(shí)例解析

 更新時(shí)間:2020年03月03日 11:07:46   作者:逃離沙漠  
這篇文章主要介紹了Mybatis Mapper接口工作原理實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

KeyWords: Mybatis 原理,源碼,Mybatis Mapper 接口實(shí)現(xiàn)類,代理模式,動(dòng)態(tài)代理,Java動(dòng)態(tài)代理,

Proxy.newProxyInstance,Mapper 映射,Mapper 實(shí)現(xiàn)

MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動(dòng)設(shè)置參數(shù)以及獲取結(jié)果集。我們?cè)谑褂?Mybaits 進(jìn)行 ,通常只需要定義幾個(gè) Mapper 接口,然后在編寫一個(gè) xml 文件,我們?cè)谂渲梦募袑懞?sql , Mybatis 幫我們完成 Mapper 接口道具體實(shí)現(xiàn)的調(diào)用。以及將結(jié)果映射到 model bean 中。

我們?cè)陧?xiàng)目中所編寫的眾多的 Mapper 類只是一個(gè)接口(interface ),根據(jù) Java 的多態(tài)性我們知道,可以使用接口接口作為形參,進(jìn)而在運(yùn)行時(shí)確定具體實(shí)現(xiàn)的對(duì)象是什么。但是,對(duì)于 Mapper 接口,我們并沒(méi)有編寫其實(shí)現(xiàn)類!Mybatis是如何找到其實(shí)現(xiàn)類,進(jìn)而完成具體的 CRUD 方法調(diào)用的呢?原理何在?

Mapper 接口是怎么找到實(shí)現(xiàn)類的

為了弄清楚 Mapper 接口是如何找到實(shí)現(xiàn)類的,我們先回憶一下 Mybatis 是怎么使用的,根據(jù)實(shí)際的例子,進(jìn)而一點(diǎn)點(diǎn)的去分析。這里的使用指的是Mybatis 單獨(dú)使用,而不是整合 spring , 因?yàn)檎?spring 的話,還需要涉及 Mapper dao 裝載到 spring 容器的問(wèn)題,spring 幫忙創(chuàng)建數(shù)據(jù)源配置等問(wèn)題。

通常我們使用 Mybatis 的主要步驟是:

  • 構(gòu)建 SqlSessionFactory ( 通過(guò) xml 配置文件 , 或者直接編寫Java代碼)
  • 從 SqlSessionFactory 中獲取 SqlSession
  • 從SqlSession 中獲取 Mapper
  • 調(diào)用 Mapper 的方法 ,例如:blogMapper.selectBlog(int blogId)

從一段代碼看起

上面我們概括了使用 Mybatis 的4個(gè)步驟。這4個(gè)步驟看起來(lái)很簡(jiǎn)單,但是用代碼寫出來(lái)就很多。我們不妨先記著這4個(gè)步驟,再去看代碼,會(huì)容易點(diǎn)。

// 1. 
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);// 添加Mapper接口
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 2. 
SqlSession session = sqlSessionFactory.openSession();
try {
 // 3. 
 BlogMapper mapper = session.getMapper(BlogMapper.class);
 // 4.
 Blog blog = mapper.selectBlog(1);
} finally {
 session.close();
}

在這塊代碼中,第 1 部分我們使用了 Java 編碼的形式來(lái)實(shí)現(xiàn) SqlSessionFactory ,也可以使用 xml 。如果使用xml的話,上面的第一部分代碼就是這樣的:

String resource = "org/mybatis/example/mybatis-config.xml"; // xml內(nèi)容就不貼了
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我們本次的目標(biāo)是弄清楚 “ Mapper 是如何找到實(shí)現(xiàn)類的 ”,我們注意上面代碼 3 , 4 的位置:

 // 3. 
 BlogMapper mapper = session.getMapper(BlogMapper.class);
 // 4.
 Blog blog = mapper.selectBlog(1);

這里 mapper 可以調(diào)用selectBlog(1) 這個(gè)方法,說(shuō)明 mapper 是個(gè)對(duì)象,因?yàn)閷?duì)象才具有方法行為實(shí)現(xiàn)啊。BlogMapper接口是不能實(shí)例化的,更沒(méi)有具體方法實(shí)現(xiàn)。我們并沒(méi)有定義一個(gè)類,讓它實(shí)現(xiàn)BlogMapper接口,而在這里它只是通過(guò)調(diào)用session.getMapper() 所得到的。由此,我們可以推斷:肯定是session.getMapper() 方法內(nèi)部產(chǎn)生了BlogMapper的實(shí)現(xiàn)類。有什么技術(shù)可以根據(jù)BlogMapper 接口生成了一個(gè)實(shí)現(xiàn)類呢?想到這里,對(duì)于有動(dòng)態(tài)代理 使用經(jīng)驗(yàn)的程序員來(lái)說(shuō),很容易想到,這背后肯定是基于動(dòng)態(tài)代理技術(shù),具體怎么實(shí)現(xiàn)的呢?下面我們來(lái)根據(jù)源碼一探究竟。

Mapper 接口的注冊(cè)

從上面的代碼中,我們知道 BlogMapper 接口的實(shí)現(xiàn)類是從session.getMapper中得來(lái)的,大概是基于動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)。我們既然能夠從SqlSession中得到BlogMapper接口的,那么我們肯定需要先在哪里把它放進(jìn)去了,然后 SqlSession 才能生成我們想要的代理類啊。上面代碼中有這么一行:

configuration.addMapper(BlogMapper.class);

跟著這個(gè) addMapper 方法的代碼實(shí)現(xiàn)是這樣的:

 public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
 }

我們看到這里 mapper 實(shí)際上被添加到 mapperRegissry 中。繼續(xù)跟進(jìn)代碼:

public class MapperRegistry {
 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers 
                         = new HashMap<Class<?>, MapperProxyFactory<?>>();
 
public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) { // 只添加接口
   if (hasMapper(type)) { // 不允許重復(fù)添加
    throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
   }
   boolean loadCompleted = false;
   try {
    knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意這里
 
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    loadCompleted = true;

   } finally {
    if (!loadCompleted) {
     knownMappers.remove(type);
    }
   }
  }
 }
}

看到這里我們知道上面所執(zhí)行的configuration.addMapper(BlogMapper.class); 其實(shí)最終被放到了HashMap中,其名為knownMappers ,knowMappers是MapperRegistry 類的一個(gè)私有屬性,它是一個(gè)HashMap 。其Key 為當(dāng)前Class對(duì)象,value 為一個(gè)MapperProxyFactory 實(shí)例。

這里我們總結(jié)一下: 諸如BlogMapper 之類的Mapper接口被添加到了MapperRegistry 中的一個(gè)HashMap中。并以 Mapper 接口的 Class 對(duì)象作為 Key , 以一個(gè)攜帶Mapper接口作為屬性的MapperProxyFactory 實(shí)例作為value 。MapperProxyFacory從名字來(lái)看,好像是一個(gè)工廠,用來(lái)創(chuàng)建Mapper Proxy的工廠。我們繼續(xù)往下看。

Mapper接口的動(dòng)態(tài)代理類的生成

上面我們已經(jīng)知道,Mapper 接口被到注冊(cè)到了MapperRegistry中——放在其名為knowMappers 的HashMap屬性中,我們?cè)谡{(diào)用Mapper接口的方法的時(shí)候,是這樣的:

BlogMapper mapper = session.getMapper(BlogMapper.class);

這里,我們跟蹤一下session.getMapper() 方法的代碼實(shí)現(xiàn),這里 SqlSession 是一個(gè)接口,他有兩個(gè)實(shí)現(xiàn)類,一個(gè)是DefaultSqlSession,另外一個(gè)是SqlSessionManager,這里我們用的是DefaultSqlSession. 為什么是DefaultSqlSession呢?因?yàn)槲覀冊(cè)诔跏蓟疭qlSessionFactory的時(shí)候所調(diào)用的SqlSessionFactoryBuilder的build()方法里邊配置的就是DefaultSqlSession, 所以,我們進(jìn)入到DefaultSession類中,看看它對(duì)session.getMapper(BlogMapper.class)是怎么實(shí)現(xiàn)的:

public class DefaultSqlSession implements SqlSession {
 private Configuration configuration; 
  @Override
 public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this); //最后會(huì)去調(diào)用MapperRegistry.getMapper
 }
}

如代碼所示,這里的 getMapper 調(diào)用了 configuration.getMapper , 這一步操作其實(shí)最終是調(diào)用了MapperRegistry,而此前我們已經(jīng)知道,MapperRegistry是存放了一個(gè)HashMap的,我們繼續(xù)跟蹤進(jìn)去看看,那么這里的get,肯定是從這個(gè)hashMap中取數(shù)據(jù)。我們來(lái)看看代碼:

public class MapperRegistry {
 
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射
 
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory =
                 (MapperProxyFactory<T>) knownMappers.get(type);
  try {
   return mapperProxyFactory.newInstance(sqlSession); // 重點(diǎn)看這里
  } catch (Exception e) {
  }
 }
}

我們調(diào)用的session.getMapper(BlogMapper.class);最終會(huì)到達(dá)上面這個(gè)方法,這個(gè)方法,根據(jù)BlogMapper的class對(duì)象,以它為key在knowMappers 中找到了對(duì)應(yīng)的value —— MapperProxyFactory(BlogMapper) 對(duì)象,然后調(diào)用這個(gè)對(duì)象的newInstance()方法。根據(jù)這個(gè)名字,我們就能猜到這個(gè)方法是創(chuàng)建了一個(gè)對(duì)象,代碼是這樣的:

public class MapperProxyFactory<T> { //映射器代理工廠

 private final Class<T> mapperInterface;
 private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

 public MapperProxyFactory(Class<T> mapperInterface) {
  this.mapperInterface = mapperInterface;
 }
 // 刪除部分代碼,便于閱讀

 @SuppressWarnings("unchecked")
 protected T newInstance(MapperProxy<T> mapperProxy) {
  //使用了JDK自帶的動(dòng)態(tài)代理生成映射器代理類的對(duì)象
  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);
 }

}

看到這里,就清楚了,最終是通過(guò)Proxy.newProxyInstance產(chǎn)生了一個(gè)BlogMapper的代理對(duì)象。Mybatis 為了完成 Mapper 接口的實(shí)現(xiàn),運(yùn)用了代理模式。具體是使用了JDK動(dòng)態(tài)代理,這個(gè)Proxy.newProxyInstance方法生成代理類的三個(gè)要素是:

  • ClassLoader —— 指定當(dāng)前接口的加載器即可
  • 當(dāng)前被代理的接口是什么 —— 這里就是 BlogMapper
  • 代理類是什么 —— 這里就是 MapperProxy

代理模式中,代理類(MapperProxy)中才真正的完成了方法調(diào)用的邏輯。我們貼出MapperProxy的代碼,如下:

public class MapperProxy<T> implements InvocationHandler, Serializable {// 實(shí)現(xiàn)了InvocationHandler
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //代理以后,所有Mapper的方法調(diào)用時(shí),都會(huì)調(diào)用這個(gè)invoke方法
  if (Object.class.equals(method.getDeclaringClass())) {
   try {
    return method.invoke(this, args); // 注意1
   } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
   }
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了緩存
  //執(zhí)行CURD
  return mapperMethod.execute(sqlSession, args); // 注意2
 }  
}

我們調(diào)用的 Blog blog = mapper.selectBlog(1); 實(shí)際上最后是會(huì)調(diào)用這個(gè)MapperProxy的invoke方法。這段代碼中,if 語(yǔ)句先判斷,我們想要調(diào)用的方法是否來(lái)自O(shè)bject類,這里的意思就是,如果我們調(diào)用toString()方法,那么是不需要做代理增強(qiáng)的,直接還調(diào)用原來(lái)的method.invoke()就行了。只有調(diào)用selectBlog()之類的方法的時(shí)候,才執(zhí)行增強(qiáng)的調(diào)用——即mapperMethod.execute(sqlSession, args);這一句代碼邏輯。

而mapperMethod.execute(sqlSession, args);這句最終就會(huì)執(zhí)行增刪改查了,代碼如下:

 public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  if (SqlCommandType.INSERT == command.getType()) {     //insert 處理,調(diào)用SqlSession的insert
   Object param = method.convertArgsToSqlCommandParam(args);
   result = rowCountResult(sqlSession.insert(command.getName(), param));
  } else if (SqlCommandType.UPDATE == command.getType()) { // update
   Object param = method.convertArgsToSqlCommandParam(args);
   result = rowCountResult(sqlSession.update(command.getName(), param));
  } else if (SqlCommandType.DELETE == command.getType()) {  // delete
   Object param = method.convertArgsToSqlCommandParam(args);
   result = rowCountResult(sqlSession.delete(command.getName(), param));
  } else if (SqlCommandType.SELECT == command.getType()) {
   // 刪除部分代碼 
  } else {
   throw new BindingException("Unknown execution method for: " + command.getName());
  }
   // 刪除部分代碼
  return result;
 }

再往下一層,就是執(zhí)行JDBC那一套了,獲取鏈接,執(zhí)行,得到ResultSet,解析ResultSet映射成JavaBean。

至此,我們已經(jīng)摸清楚了Blog blog = mapper.selectBlog(1); 中,BlogMapper接口調(diào)用到得到數(shù)據(jù)庫(kù)數(shù)據(jù)過(guò)程中,Mybaitis 是如何為接口生成實(shí)現(xiàn)類的,以及在哪里出發(fā)了最終的CRUD調(diào)用。實(shí)際上,如果我們?cè)谡{(diào)用Blog blog = mapper.selectBlog(1);之前,把從slqSession中得到的 mapper 對(duì)象打印出來(lái)就會(huì)看到,輸出大概是這樣的:

com.sun.proxy.$Proxy17

動(dòng)態(tài)代理沒(méi)錯(cuò)吧,Java動(dòng)態(tài)代理實(shí)在是太美妙了。

總結(jié)

上面我們用層層深入的方式摸清楚了 Mapper接口是如何找到實(shí)現(xiàn)類的。我們分析了 Mapper接口是如何注冊(cè)的,Mapper接口是如何產(chǎn)生動(dòng)態(tài)代理對(duì)象的,Maper接口方法最終是如何執(zhí)行的??偨Y(jié)起來(lái)主要就是這幾個(gè)點(diǎn):

1. Mapper 接口在初始SqlSessionFactory 注冊(cè)的。

2. Mapper 接口注冊(cè)在了名為 MapperRegistry 類的 HashMap中, key = Mapper class value = 創(chuàng)建當(dāng)前Mapper的工廠。

3. Mapper 注冊(cè)之后,可以從SqlSession中g(shù)et

4. SqlSession.getMapper 運(yùn)用了 JDK動(dòng)態(tài)代理,產(chǎn)生了目標(biāo)Mapper接口的代理對(duì)象。

5. 動(dòng)態(tài)代理的 代理類是 MapperProxy ,這里邊最終完成了增刪改查方法的調(diào)用。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot整合logback日志的詳細(xì)步驟

    SpringBoot整合logback日志的詳細(xì)步驟

    這篇文章主要介紹了SpringBoot整合logback日志的詳細(xì)步驟,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • 用Eclipse 創(chuàng)建一個(gè)簡(jiǎn)單的web項(xiàng)目(圖文教程)

    用Eclipse 創(chuàng)建一個(gè)簡(jiǎn)單的web項(xiàng)目(圖文教程)

    下面小編就為大家?guī)?lái)一篇用Eclipse 創(chuàng)建一個(gè)簡(jiǎn)單的web項(xiàng)目(圖文教程)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • IDEA Project不顯示/缺失文件問(wèn)題及解決

    IDEA Project不顯示/缺失文件問(wèn)題及解決

    在側(cè)邊欄的project模式下,如果發(fā)現(xiàn)缺少部分文件,可以嘗試關(guān)閉項(xiàng)目,打開項(xiàng)目所在目錄,刪除目錄下的.idea文件夾,然后重新打開項(xiàng)目即可解決
    2024-11-11
  • Java SpringBoot實(shí)現(xiàn)帶界面的代碼生成器詳解

    Java SpringBoot實(shí)現(xiàn)帶界面的代碼生成器詳解

    這篇文章主要介紹了Java SpringBoot如何實(shí)現(xiàn)帶界面的代碼生成器,幫助大家更好的理解和使用Java SpringBoot編程語(yǔ)言,感興趣的朋友可以了解下
    2021-09-09
  • Java8函數(shù)式接口java.util.function速查大全

    Java8函數(shù)式接口java.util.function速查大全

    因?yàn)镴ava8引入了函數(shù)式接口,在java.util.function包含了幾大類函數(shù)式接口聲明,這篇文章主要給大家介紹了關(guān)于Java8函數(shù)式接口java.util.function速查的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • Mybatis-Plus主鍵生成策略的方法

    Mybatis-Plus主鍵生成策略的方法

    本文主要介紹了Mybatis-Plus主鍵生成策略的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • Java中的注解、元注解詳細(xì)解析

    Java中的注解、元注解詳細(xì)解析

    這篇文章主要介紹了Java中的注解、元注解詳細(xì)解析,注解也叫元數(shù)據(jù),與類、接口、枚舉是在同一個(gè)層次,它可以聲明在包、類、字段、方法、局部變量、方法參數(shù)等的前面,用來(lái)對(duì)這些元素進(jìn)行說(shuō)明,注釋,需要的朋友可以參考下
    2023-11-11
  • 關(guān)于idea中ssm框架的編碼問(wèn)題分析

    關(guān)于idea中ssm框架的編碼問(wèn)題分析

    在實(shí)際開發(fā)中需要將操作系統(tǒng)編碼、文件編碼、頁(yè)面編碼以及tomcat服務(wù)器編碼保持一致,而tomcat在默認(rèn)情況下是使用UTF-8,這就使得其打印的日志文件出現(xiàn)中文亂碼,因此在一般情況下,只需要將tomcat服務(wù)器的編碼改為GBK即可
    2021-06-06
  • Java  Object類中的常用API介紹

    Java  Object類中的常用API介紹

    這篇文章主要介紹了Java  Object類中的常用API介紹,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-11-11
  • 淺析SpringBoot統(tǒng)一返回結(jié)果的實(shí)現(xiàn)

    淺析SpringBoot統(tǒng)一返回結(jié)果的實(shí)現(xiàn)

    前后端開發(fā)過(guò)程中數(shù)據(jù)交互規(guī)范化是一件非常重要的事情,不僅可以減少前后端交互過(guò)程中出現(xiàn)的問(wèn)題,也讓代碼邏輯更加具有條理,下面小編就和大家講講SpringBoot如何統(tǒng)一返回結(jié)果的吧
    2023-07-07

最新評(píng)論

江城| 宜兴市| 浮梁县| 洪江市| 闻喜县| 安宁市| 鞍山市| 龙门县| 肃北| 尼木县| 高平市| 三明市| 清镇市| 廊坊市| 砀山县| 方城县| 靖远县| 香河县| 轮台县| 专栏| 绥中县| 靖宇县| 天长市| 龙岩市| 华安县| 临朐县| 金阳县| 馆陶县| 满洲里市| 长治县| 木里| 台南市| 蕉岭县| 财经| 宁远县| 长汀县| 疏附县| 盐边县| 杨浦区| 崇信县| 汶上县|