欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解析 MyBatis 中 Mapper 生效的來龍去脈

 更新時間:2021年08月09日 10:32:37   作者:J3-白起  
這篇文章主要介紹了解析 MyBatis 中 Mapper 生效的前因后果,介紹了mybatis基本使用及源碼分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

最近閑了快有半個多月了,一直提不起興致再去看一些書籍(沒有以前瘋狂吸食知識的欲望了😓)。

不過這一兩天不知道是什么筋搭錯了非常想寫點什么,但又不知道寫點啥(苦惱)。所以我就結合了一下本人工作中經(jīng)常用到但沒有深入的技術下手了,最后思來想去就選擇了 MyBatis 中 Mapper 文件這一塊的知識內容入手了。

以前只是知道寫一個 Mapper 接口,對應著再去寫一個 Mapper.xml 文件然后將 Mapper 接口位置和 Mapper.xml 文件位置通過 MyBatisConfig.xml 的配置文件關聯(lián)起來就可以非常方便的操作訪問數(shù)據(jù)庫,但究其原因確是說不上個所以然來(汗顏)。

那既然搞出了前因,后面就一起往下學咯!

在這里插入圖片描述

一、MyBatis基本使用

一切都從最簡單的開始,所以先來回顧一下其基本的使用(不會吧不會吧,最基本的hello world別忘了)。

步驟:

1、首先我們要創(chuàng)建一個maven工程

2、添加MyBatis的依賴及MySQL依賴,如下:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

3、再添加一個測試單元依賴吧,等會要通過測試單元進行測試

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

OK,到這一步項目的基本環(huán)境就搭建完畢了,下面就是正式的使用 MyBatis 框架相關的內容了。

1.1 編寫配置文件

在資源目錄下面創(chuàng)建下面兩個配置文件:

這里我們先準備數(shù)據(jù)庫連接信息的配置類:jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatistest?useUnicode=true&amp;characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

接著就是最重要的一個配置類了:MyBatisConfig.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 導入數(shù)據(jù)庫配置文件的信息-->
    <properties resource="jdbc.properties"></properties>
    <!-- 配置setting屬性-->
    <settings>
        <!-- 開啟了一個駝峰命名規(guī)則-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!-- 日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"></setting>
    </settings>
    <!-- 配置數(shù)據(jù)庫-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 配置連接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--  mappers中注冊我們所有寫的dao接口的實現(xiàn)(映射)文件-->
    <mappers>
        <mapper resource="/.../IUserMapper.xml"/>
        <!-- 如果映射文件有十幾百個的話,可以用下面的全局注冊
    		<package name="文件所在包路徑"></package>
    		<package name="cn.liuliang.Dao"></package>
    	-->
    </mappers>
</configuration>

1.2 編寫Mapper接口及測試方法

Mapper接口類

public interface IUserMapper {

    /**
     * 查詢所有用戶
     * @return
     */
     List<User> findAll();

}

開始測試

public class MyBatisTest {

    @Test
    public void test01() throws IOException {

        // 讀取配置文件
        InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
        // 創(chuàng)建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        // 獲得會話
        SqlSession session=sqlSessionFactory.openSession();
        // 得到代理
        IUserMapper iUserMapper =session.getMapper(IUserMapper.class);
        // 查詢數(shù)據(jù)庫
        List<User> userList= iUserMapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }
    }

}

1.3 結果

在這里插入圖片描述

SQL和結果都打印出來了😁。

以后,只要是對數(shù)據(jù)庫的操作,我們就只需要編寫 Mapper 接口和其對應的 xml 文件就可以非??焖俨僮鲾?shù)據(jù)庫,對比以前原生JDBC操作什么拼接SQL、結果集映射、資源關閉一大堆操作讓我們開發(fā)人員來處理,也太雞肋了吧!所以對于這個 MyBatis 持久層框架我只想說(牛逼)。

下面就要全程高能哦!但其實也很簡單了,它就只是把原生操作的 JDBC 進行了封裝,暴露出按照它所定義的簡單規(guī)則走而已,多的不說了,你們有資格一睹 MyBatis 源碼的芳容了。

在這里插入圖片描述

二、源碼分析

既然要分析源碼了,那么從什么地方入手呢!— 測試方法

通過測試方法,我們可以知道 MyBatis 會先加載資源文件(MyBatisConfig.xml),因為這文件是一切的開始,通過這個文件可以知道數(shù)據(jù)源、特性(日志,駝峰命名…)、Mapper 文件等一系列信息。

2.1 通過配置文件構建出 SqlSessionFactory

第一個類名出現(xiàn)了:SqlSessionFactory ,它的類圖如下:

在這里插入圖片描述

簡單熟悉一下圖中出現(xiàn)的名字吧:

SqlSessionFactory接口:SqlSessionFactory 負責創(chuàng)建 SqlSession 對象,其中只包含了多個 openSession() 方法的重載,可以通過其參數(shù)指定事務的隔離級別、底層使用 Executor 的類型以及是否自動提交事務等方面的配置。

DefaultSqlSessionFactory類:一個具體的工廠,實現(xiàn)了 SqlSessionFactory 接口。它主要提供了兩種創(chuàng)建 DefaultSqlSession 對象的方式:

  1. 通過數(shù)據(jù)源獲取數(shù)據(jù)庫連接,并創(chuàng)建 Executor 對象及 DefaultSqlSession 。
  2. 通過用戶提供的數(shù)據(jù)連接對象,DefaultSqlSessionFactory 會使用該數(shù)據(jù)庫連接對象創(chuàng)建 Executor 對象及 DefaultSqlSession。

SqlSessionManager類:同時實現(xiàn)了 SqlSession 接口和 SqlSessionFactory 接口 ,也就同時提供了SqlSessionFactory 創(chuàng)建 SqlSession 以及 SqlSession 操縱數(shù)據(jù)庫的功能。

SqlSession接口:是mybatis的核心操作類,其中對數(shù)據(jù)庫的crud都封裝在這個中,是一個頂級接口,其中默認實現(xiàn)類是DefaultSqlSession這個類。

  • DefaultSqlSession類:默認 SqlSession 接口的 CRUD 實現(xiàn)類,且 DefaultSqlsession 不是線程安全的(對于線程安全,關注session和connnect的關系就好了)

好了開始分析,從第一行代碼入手:

// 讀取配置文件
InputStream in= Resources.getResourceAsStream("MyBatisConfig.xml");
// 創(chuàng)建sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);

SqlSessionFactoryBuilder # build

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    // ...
    // 根據(jù)文件流,創(chuàng)建 XMLConfigBuilder 對象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    // 先解析 配置文件,然后構建出 SqlSessionFactory對象
    return build(parser.parse());
    // ...
}

最終會創(chuàng)建一個 DefaultSqlSessionFactory 對象返回出去

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

流程如下:

在這里插入圖片描述

2.2 獲取 SqlSession 對象

在獲取到會話工廠之后,就是根據(jù)工廠獲得具體的會話了。

代碼入口:

// 獲得會話
SqlSession session=sqlSessionFactory.openSession();

調用:DefaultSqlSessionFactory # openSession()

public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

最終來到:DefaultSqlSessionFactory # openSessionFromDataSource()

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // 根據(jù)配置文件 configuration 獲取對應的會話環(huán)境(包括事物,數(shù)據(jù)源)
        final Environment environment = configuration.getEnvironment();
        // 獲取事物工廠
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 根據(jù)數(shù)據(jù)源,配置事物,autoCommit:是否自動提交事物
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 根據(jù)配置獲取執(zhí)行器(最終都是它執(zhí)行對應的數(shù)據(jù)庫操作)
        final Executor executor = configuration.newExecutor(tx, execType);
        // 準備好上面的信息之后,都封裝到默認會話對象中返回出去
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

在獲取 SqlSession 對象的過程中,都是根據(jù)默認的會話工廠,從工廠中獲取對應的會話。這樣在我看來非常的不錯,因為獲取一個數(shù)據(jù)庫的操作會話是需要配置非常多的屬性的,包括數(shù)據(jù)源配置、事物配置等。但是有了這個創(chuàng)建會話工廠類之后,那么一切就變得簡單起來了,工廠囊括了所有的細節(jié),只需要我們調一個對外的 API 我們就可以獲得對應的 SqlSession 對象(工廠幫我們做了細節(jié)),進而操作數(shù)據(jù)庫,讀了上面的代碼就是一個很好的提現(xiàn)😀。

提一點:

配置文件(MyBatisConfig.xml)構造出默認會話工廠(SqlSessionFactory),工廠再創(chuàng)建出具體的操作數(shù)據(jù)庫會話(SqlSession)

2.3 根據(jù) SqlSession 獲取 Mapper 代理

在上面,已經(jīng)分析了如何獲取一個會話的源碼,那我們得到一個會話之后,就是要根據(jù)具體的 Mapper 接口獲得對應的操作數(shù)據(jù)庫代理對象了,就是下面這段代碼:

// 得到代理
IUserMapper iUserMapper =session.getMapper(IUserMapper.class);

點進去看看

因為 session 對象是由 DefaultSqlSessionFactory 創(chuàng)建出來的 DefaultSqlSession,所以該代碼位于此類中

public <T> T getMapper(Class<T> type) {
    // 根據(jù)配置類,獲取 Mapper 
    return configuration.getMapper(type, this);
}

點進去:Configuration # getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 從 mapperRegistry 中獲取具體 Mapper
    return mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry:可以理解為 Mapper 接口的注冊中心,里面存放了所有 Mapper 接口相關屬性。

MapperRegistry# getMapper

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // knownMappers,一個Map,存放 Mapper 代理工廠
    // 在初始化的時候根據(jù)配置文件已經(jīng)將所有配置的 Mapper 接口注冊到此了
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 具體代理生成
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

點進具體代理:MapperProxyFactory # newInstance

public T newInstance(SqlSession sqlSession) {
    // 根據(jù) SqlSession 和 Mapper 接口生成代理對象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 真正代理,如下
    return newInstance(mapperProxy);
}
// 下面就是根據(jù) JDK 原生 API 進行代理了,由此返回代理對象給用戶使用
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

以上就是 Mapper 接口被代理的全部流程了,其中先是根據(jù)會話去獲得對應的 Mapper 但其內部調用的是 Mapper 注冊中心(MapperRegistry)獲取,這里面有所有配置的 Mapper 接口,在 MapperRegistry 中維護了一個 Map 鍵為 Class 值是 MapperProxyFactory ,這樣就可以獲得要代理 Mapper 接口的代理工廠,最后通過這個工廠生成我們想要的 Mapper 返回用戶。

流程不復雜,就是里面出現(xiàn)了很多 MapperXXX 相關的類,那么下面我梳理一下這些類關系圖如下:

在這里插入圖片描述

對于具體的代理執(zhí)行類這一步就要到執(zhí)行這一塊了,當用戶通過我們返回的代理類(Mapper 接口)執(zhí)行對應方法時,就會走到圖中涉及的類。

按照慣例,來個流程圖吧!

在這里插入圖片描述

2.4 通過 Mapper 代理,執(zhí)行方法操作數(shù)據(jù)庫

上面的所有分析,都是為了等到一個具體的操作數(shù)據(jù)庫的一個橋梁,那就是 Mapper 代理了(iUserMapper)。

接下來就是分析最后一步了,真正操作數(shù)據(jù)庫,代碼如下:

// 查詢數(shù)據(jù)庫
List<User> userList= iUserMapper.findAll();
for (User user : userList) {
    System.out.println(user);
}

對于 iUserMapper 對象,我們知道他是代理去執(zhí)行的,所以直接點進去的話根本行不通,那么我們可以通過 Debug 進去看看。

在這里插入圖片描述

MapperProxy # invoke

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 方法的類為 object 直接通過原始 JDK 去執(zhí)行
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            // 根據(jù)方法,獲得方法的執(zhí)行器后再執(zhí)行代理方法
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

我們先進入 MapperProxy # cachedInvoker 這個方法看看

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        // 先查緩存,有就返回,沒有就創(chuàng)建
        MapperMethodInvoker invoker = methodCache.get(method);
        if (invoker != null) {
            return invoker;
        }

        return methodCache.computeIfAbsent(method, m -> {
            // ...
            // 返回 PlainMethodInvoker 類型的 Mapper 方法執(zhí)行器
            return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
            // ...
        });
    } catch (RuntimeException re) {
        Throwable cause = re.getCause();
        throw cause == null ? re : cause;
    }
}

接著進入 PlainMethodInvoker# invoke 這個方法

public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    // 調用 mapperMethod 對象的 execute 方法去真正執(zhí)行了
    return mapperMethod.execute(sqlSession, args);
}

真正執(zhí)行的開始 execute

MapperMethod # execute

public Object execute(SqlSession sqlSession, Object[] args) {
    // 這里面內容比較多,我簡單分析一下
    // 1)封裝參數(shù)
    // 2)根據(jù)對應的執(zhí)行類型(INSERT,UPDATE,DELETE,SELECT),執(zhí)行對應的方法
    // 3)根據(jù)參數(shù),執(zhí)行類型封裝對應的 sql
    // 4)操作原生 JDBC API 執(zhí)行數(shù)據(jù)庫操作
    // 5)封裝結果集,返回出去
}

我們 Debug 這個方法最后一步,看看結果:

在這里插入圖片描述

到此,我們的 Mapper 接口及文件生效的原理,就全部過了一邊,是不是覺得不是很難呢!

在分析這一塊源碼時,本人理解的步驟就是:

  • 一步步點進源碼看。
  • 畫出流程圖,不清楚的就 Debug。
  • 很重要一點,對很多出現(xiàn)類似名字的類,一定要畫出類圖,搞清楚關系在往下走(助于理解每個類的職責)。
  • 最后,那就是寫點筆記了,畢竟好記性不如爛筆頭。

2.5 整體流程圖

在這里插入圖片描述

到此這篇關于解析 MyBatis 中 Mapper 生效的前因后果的文章就介紹到這了,更多相關 MyBatis 中 Mapper 生效內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java中stream是什么及如何使用

    Java中stream是什么及如何使用

    在Java中,Stream(流)是一種用于操作集合(Collection)、數(shù)組等數(shù)據(jù)源的API,Stream的主要作用是進行數(shù)據(jù)的轉換、篩選、聚合等操作,可以極大地簡化對數(shù)據(jù)的處理,本文給大家介紹Java中stream是什么?有什么作用?如何使用?感興趣的朋友一起看看吧
    2023-10-10
  • 淺談Java 中的單元測試

    淺談Java 中的單元測試

    這篇文章主要介紹了Java 中的單元測試的相關資料,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-09-09
  • SpringMVC之簡單的增刪改查示例(SSM整合)

    SpringMVC之簡單的增刪改查示例(SSM整合)

    本篇文章主要介紹了SpringMVC之簡單的增刪改查示例(SSM整合),這個例子是基于SpringMVC+Spring+Mybatis實現(xiàn)的。有興趣的可以了解一下。
    2017-03-03
  • Java制作證書的工具keytool用法詳解

    Java制作證書的工具keytool用法詳解

    本文主要介紹了Java制作證書的工具keytool用法詳解,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • Spring-Task定時任務的使用介紹

    Spring-Task定時任務的使用介紹

    目前springboot應用廣泛,因此對于spring-task直接基于springboot框架介紹,不涉及xml配置。本文直接介紹spring-task的使用方法,需要的可以參考一下
    2022-11-11
  • Java中URL傳中文時亂碼的解決方法

    Java中URL傳中文時亂碼的解決方法

    為什么說亂碼是中國程序員無法避免的話題呢?這個主要是編碼機制上的原因,大家都知道中文和英文的編碼格式不一樣,解碼自然也不一樣!這篇文章就給大家分享了Java中URL傳中文時亂碼的解決方法,有需要的朋友們可以參考借鑒。
    2016-10-10
  • SpringAop日志找不到方法的處理

    SpringAop日志找不到方法的處理

    這篇文章主要介紹了SpringAop日志找不到方法的處理方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • IDEA在Maven項目中使用本地jar包的方法

    IDEA在Maven項目中使用本地jar包的方法

    我們在拿到舊項目的時候,經(jīng)常會遇到一種情況,就是這個項目的maven中依賴了一個本地的jar包,這種情況就需要引入這個jar包,所以本文給大家介紹了IDEA在Maven項目中使用本地jar包的方法,需要的朋友可以參考下
    2024-04-04
  • MyBatis中如何查詢某個時間段內的數(shù)據(jù)

    MyBatis中如何查詢某個時間段內的數(shù)據(jù)

    這篇文章主要介紹了MyBatis中如何查詢某個時間段內的數(shù)據(jù),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • PageHelper在springboot中的使用方式

    PageHelper在springboot中的使用方式

    這篇文章主要介紹了PageHelper在springboot中的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-05-05

最新評論