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

MyBatis的通俗理解:SqlSession.getMapper()源碼解讀

 更新時間:2023年03月01日 14:13:03   作者:Lin_Dong_Tian  
這篇文章主要介紹了MyBatis的通俗理解:SqlSession.getMapper()源碼解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

什么是 MyBatis?

直接看官方文檔:https://mybatis.org/mybatis-3/zh/index.html

從上面我們了解到:

1、MyBatis 是一款優(yōu)秀的持久層框架

2、MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。

原理解析

1、程序員和Mybatis 和數(shù)據(jù)的關(guān)系:人通過mybatis框架來操作數(shù)據(jù)庫。

2、思考問題并解決

問題1:首先我們必須告訴MyBatis要怎么操作數(shù)據(jù)庫?

我們把可以通過XML配置文件或者注解的方式,MyBatis提供了一個類Configuration, Mybatis 讀取XML配置文件后會將內(nèi)容放在一個Configuration類中,Configuration類會存在整個Mybatis生命周期,以便重復(fù)讀取。

問題2:想要Mybatis與數(shù)據(jù)庫打交道,就要有一個類似于JDBC的Connection對象,在MyBatis中叫SqlSesion,所以我們要有一個SqlSession。

Mybatis 讀取XML配置文件后會將內(nèi)容放在一個Configuration類中,SqlSessionFactoryBuilder會讀取Configuration類中信息創(chuàng)建SqlSessionFactory。SqlSessionFactory創(chuàng)建SqlSession。

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
 
SqlSession sqlSession=null;
try{
    sqlSession=sqlSessionFactory.openSession();
    //some code
    sqlSession.commit();
} catch(Exception ex){
    sqlSession.roolback();
} finally{
    if(sqlSession!=null){
        sqlSession.close();
    }
}

關(guān)于SqlSessionFactory的創(chuàng)建,Mybatis采用構(gòu)造模式來完成創(chuàng)建。

  • 第一步:XMLConfigBuilder解析XML配置,讀出配置參數(shù),存入Configuration類中。
  • 第二步:Configuration類創(chuàng)建SqlSessionFactory。(DefaultSqlSessionFactory的構(gòu)造函數(shù)傳入Configuration類)

深入了解:SqlSessionFactoryBuilder.builder(inputStream)

//該方法.builder中的主要內(nèi)容: 
 
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
SqlSessionFactory localSqlSessionFactory = build(parser.parse());
 
//build(parser.parse())方法實則為:
 
public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
 }

問題3:SqlSession能干什么?

SqlSession用途主要有兩種

①. 獲取對應(yīng)的Mapper,讓映射器通過命名空間和方法名稱找到對應(yīng)的SQL,發(fā)送給數(shù)據(jù)庫執(zhí)行后返回結(jié)果。

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);

②. 直接使用SqlSession,通過命名信息去執(zhí)行SQL返回結(jié)果,該方式是IBatis版本留下的,SqlSession通過Update、Select、Insert、Delete等方法操作。

Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);

Mybatis底層利用JDK動態(tài)代理技術(shù)實現(xiàn)該接口,底層最后還是使用的IBatis中SqlSession通過Update、Select、Insert、Delete等方法操作。

問題4:上面說到Mybatis底層利用JDK動態(tài)代理技術(shù)實現(xiàn)該接口,但是我們在使用MyBatis的時候,都是只寫接口不用寫實現(xiàn)類,為什么呢?

為什么要使用動態(tài)代理?可以在不修改別代理對象代碼的基礎(chǔ)上,通過擴(kuò)展代理類,進(jìn)行一些功能的附加與增強(qiáng)。

我們先看看傳統(tǒng)的JDK動態(tài)代理:

1、首先有一個接口

public interface Calculate {
    void add(int i, int j);
}

2、然后是接口的實現(xiàn)類

public class CalculateImp implements Calculate {
    @Override
    public void add(int i, int j) {
        System.out.println("result = " + (i + j));
    }
}

3、代理類實現(xiàn)InvocationHandler

public class CalculateProxy implements InvocationHandler {
    private Object target;
    //總要讓我知道要代理誰吧:構(gòu)造方法中把傳入一個代理類的實例
    public CalculateProxy(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("====== Before() ======");
        method.invoke(target, args);
        System.out.println("====== After () ======");
        return null;
    }
}

4、拿到代理對象,操作接口方法

public class test {
    public static void main(String[] args) {
        InvocationHandler handler = new CalculateProxy(new CalculateImp());
        Calculate calculateProxy =
                (Calculate) Proxy.newProxyInstance(Calculate.class.getClassLoader(),
                        new Class[]{Calculate.class},
                        handler);
        calculateProxy.add(10,20);
    }
}

Proxy.newProxyInstance()方法有三個參數(shù):

1. 類加載器(Class Loader)

2. 需要實現(xiàn)的接口數(shù)組

3. InvocationHandler接口。所有動態(tài)代理類的方法調(diào)用,都會交由InvocationHandler接口實現(xiàn)類里的invoke()方法去處理。這是動態(tài)代理的關(guān)鍵所在。

回到我們之前的問題,我們并沒有接口實現(xiàn)類,那沒有實現(xiàn)類還為什么還能調(diào)用方法操作。其實是這樣的:

操作數(shù)據(jù)庫主要是通過SQL語句,那么只要找到SQL語句然后執(zhí)行不就可以!

通過例子分析:

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

這里 mapper 可以調(diào)用selectBlog(1) 這個方法,說明 mapper 是個對象,因為對象才具有方法行為實現(xiàn)啊。BlogMapper接口是不能實例化的,更沒有具體方法實現(xiàn)。

我們并沒有定義一個類,讓它實現(xiàn)BlogMapper接口,而在這里它只是通過調(diào)用session.getMapper() 所得到的。

由此,我們可以推斷:肯定是session.getMapper() 方法內(nèi)部產(chǎn)生了BlogMapper的實現(xiàn)類。有什么技術(shù)可以根據(jù)BlogMapper 接口生成了一個實現(xiàn)類呢?想到這里,對于有動態(tài)代理 。

Mapper 接口的注冊

我們既然能夠從SqlSession中得到BlogMapper接口的,那么我們肯定需要先在哪里把它放進(jìn)去了,然后 SqlSession 才能生成我們想要的代理類啊。

我們可以從getMapper()聯(lián)系,可能會有一個setMapper()或者addMapper()方法。確實是有!

configuration.addMapper(BlogMapper.class);

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

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

我們看到這里 mapper 實際上被添加到 mapperRegistry (mapper注冊器)中。繼續(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);
        }
      }
    }
  }
}

我們首先看到MapperRegistry類,有一個私有屬性knowMappers,它是一個HashMap 。

Key 為當(dāng)前Class對象,value 為一個MapperProxyFactory實例

在MapperRegistry類的addMapper()方法中,knownMappers.put(type, new MapperProxyFactory<T>(type));相當(dāng)于把:諸如BlogMapper之類的Mapper接口被添加到了MapperRegistry 中的一個HashMap中。

并以 Mapper 接口的 Class 對象作為 Key , 以一個攜帶Mapper接口作為屬性的MapperProxyFactory實例作為value 。

MapperProxyFactory從名字來看,好像是一個工廠,用來創(chuàng)建Mapper Proxy的工廠。

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

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

這里,我們跟蹤一下session.getMapper() 方法的代碼實現(xiàn),這里 SqlSession 是一個接口,他有兩個實現(xiàn)類,

一個是DefaultSqlSession,另外一個是SqlSessionManager,

這里我們用的是DefaultSqlSession. 為什么是DefaultSqlSession呢?因為我們在初始化SqlSessionFactory的時候所調(diào)用的SqlSessionFactoryBuilder的build()方法里邊配置的就是DefaultSqlSession, 所以,我們進(jìn)入到DefaultSession類中,看看它對session.getMapper(BlogMapper.class)是怎么實現(xiàn)的:

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

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

我們來看看代碼:

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); // 重點看這里
    } catch (Exception e) {
    }
  }
}

我們調(diào)用的session.getMapper(BlogMapper.class);最終會到達(dá)上面這個方法,這個方法,根據(jù)BlogMapper的class對象,以它為keyknowMappers 中找到了對應(yīng)的value —— MapperProxyFactory(BlogMapper) 對象,然后調(diào)用這個對象的newInstance()方法。

根據(jù)這個名字,我們就能猜到這個方法是創(chuàng)建了一個對象,代碼是這樣的:

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自帶的動態(tài)代理生成映射器代理類的對象
    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);
  }
 
}

看到這里,就清楚了,最終是通過Proxy.newProxyInstance產(chǎn)生了一個BlogMapper的代理對象。

Mybatis 為了完成 Mapper 接口的實現(xiàn),運(yùn)用了代理模式。

具體是使用了JDK動態(tài)代理,這個Proxy.newProxyInstance方法生成代理類的三個要素是:

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

代理模式中,代理類(MapperProxy)中才真正的完成了方法調(diào)用的邏輯。

我們貼出MapperProxy的代碼,如下:

public class MapperProxy<T> implements InvocationHandler, Serializable {// 實現(xiàn)了InvocationHandler
  
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //代理以后,所有Mapper的方法調(diào)用時,都會調(diào)用這個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); 實際上最后是會調(diào)用這個MapperProxyinvoke方法。

這段代碼中,if 語句先判斷,我們想要調(diào)用的方法是否來自O(shè)bject類,這里的意思就是,如果我們調(diào)用toString()方法,那么是不需要做代理增強(qiáng)的,直接還調(diào)用原來的method.invoke()就行了。

只有調(diào)用selectBlog()之類的方法的時候,才執(zhí)行增強(qiáng)的調(diào)用——即mapperMethod.execute(sqlSession, args);這一句代碼邏輯。

mapperMethod.execute(sqlSession, args);這句最終就會執(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。

總結(jié)一下各個過程

1、Mybatis 讀取XML配置文件后會將內(nèi)容放在一個Configuration類中,SqlSessionFactoryBuilder會讀取Configuration類中信息創(chuàng)建SqlSessionFactory。

2、在初始化SqlSessionFactory時,Mapper 接口進(jìn)行注冊,注冊在了名為 MapperRegistry 類的 HashMap中,key = Mapper class, value = 創(chuàng)建當(dāng)前Mapper的工廠。

3、SqlSessionFactory創(chuàng)建SqlSession。

4、SqlSession中可以通過getMapper()拿到代理對象,SqlSession.getMapper 運(yùn)用了 JDK動態(tài)代理,產(chǎn)生了目標(biāo)Mapper接口的代理對象。

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

最后

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • SpringBoot整合Vue實現(xiàn)微信掃碼支付以及微信退款功能詳解

    SpringBoot整合Vue實現(xiàn)微信掃碼支付以及微信退款功能詳解

    最近公司要在微信公眾號上做一個活動預(yù)報名,活動的門票等需要在微信中支付,下面這篇文章主要給大家介紹了關(guān)于SpringBoot整合Vue實現(xiàn)微信掃碼支付以及微信退款功能的相關(guān)資料,需要的朋友可以參考下
    2022-05-05
  • Spring中@EnableScheduling注解的工作原理詳解

    Spring中@EnableScheduling注解的工作原理詳解

    這篇文章主要介紹了Spring中@EnableScheduling注解的工作原理詳解,@EnableScheduling是 Spring Framework 提供的一個注解,用于啟用Spring的定時任務(wù)(Scheduling)功能,需要的朋友可以參考下
    2024-01-01
  • 詳解Spring-bean的循環(huán)依賴以及解決方式

    詳解Spring-bean的循環(huán)依賴以及解決方式

    這篇文章主要介紹了詳解Spring-bean的循環(huán)依賴以及解決方式,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • java安全編碼指南之:Mutability可變性詳解

    java安全編碼指南之:Mutability可變性詳解

    這篇文章主要介紹了java安全編碼指南之:Mutability可變性詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • SpringBoot對靜態(tài)資源的映射規(guī)則詳解

    SpringBoot對靜態(tài)資源的映射規(guī)則詳解

    在Web應(yīng)用中會涉及到大量的靜態(tài)資源,例如 JS、CSS和HTML等,我們知道,Spring MVC 導(dǎo)入靜態(tài)資源文件時,需要配置靜態(tài)資源的映射,但在 SpringBoot 中則不再需要進(jìn)行此項配置,因為SpringBoot已經(jīng)默認(rèn)完成了這一工作,本文給大家介紹了SpringBoot對靜態(tài)資源的映射規(guī)則詳
    2024-12-12
  • Ribbon的饑餓加載(eager-load)模式解讀

    Ribbon的饑餓加載(eager-load)模式解讀

    這篇文章主要介紹了Ribbon的饑餓加載(eager-load)模式解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • JAVA項目如何打包部署到Linux服務(wù)器上

    JAVA項目如何打包部署到Linux服務(wù)器上

    本文詳細(xì)介紹了在服務(wù)器上部署環(huán)境包括JDK、MySQL、Tomcat的設(shè)置,以及使用Idea-Maven-SpringBoot進(jìn)行jar包打包部署的流程,內(nèi)容涵蓋了MySQL配置注意事項、pom.xml配置、打包命令等關(guān)鍵步驟,同時,也提供了如何將jar包上傳到Linux服務(wù)器并運(yùn)行的具體方法
    2024-10-10
  • Java多種經(jīng)典排序算法(含動態(tài)圖)

    Java多種經(jīng)典排序算法(含動態(tài)圖)

    排序算法是老生常談的了,但是在面試中也有會被問到,例如有時候,在考察算法能力的時候,不讓你寫算法,就讓你描述一下,某個排序算法的思想以及時間復(fù)雜度或空間復(fù)雜度。我就遇到過,直接問快排的,所以這次我就總結(jié)梳理一下經(jīng)典的十大排序算法以及它們的模板代碼
    2021-04-04
  • 解決springboot自定義配置Boolean屬性不能生效的問題

    解決springboot自定義配置Boolean屬性不能生效的問題

    這篇文章主要介紹了解決springboot自定義配置Boolean屬性不能生效的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java編程倒計時實現(xiàn)方法示例

    Java編程倒計時實現(xiàn)方法示例

    這篇文章主要介紹了Java編程倒計時實現(xiàn)的三個示例,三種實現(xiàn)方法,具有一定參考價值,需要的朋友可以了解下。
    2017-09-09

最新評論

汶川县| 萍乡市| 龙山县| 金平| 竹北市| 贡觉县| 横山县| 南投市| 临高县| 松桃| 南昌市| 涞源县| 三台县| 霍邱县| 东海县| 沅江市| 武隆县| 长丰县| 云安县| 四会市| 房山区| 勃利县| 吉安县| 乐清市| 确山县| 英德市| 浮梁县| 余江县| 宁安市| 辉南县| 迁西县| 边坝县| 吉首市| 福建省| 景德镇市| 泸西县| 阿图什市| 封丘县| 五河县| 福泉市| 新余市|