首頁  >  文章  >  Java  >  一週學完MyBatis源碼,萬字總結

一週學完MyBatis源碼,萬字總結

Java后端技术全栈
Java后端技术全栈轉載
2023-08-23 14:51:141220瀏覽

眾所周知,MyBatis是對##JDBC進行封裝而成的產品,所以,聊聊MyBatis原始碼之前我們得先了解JDBC

JDCB

#JDBC案例:

public class JdbcDemo {
    public static final String URL = "jdbc:mysql://localhost:3306/mblog";
    public static final String USER = "root";
    public static final String PASSWORD = "123456";

    public static void main(String[] args) throws Exception { 
        Class.forName("com.mysql.jdbc.Driver"); 
        Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); 
        Statement stmt = conn.createStatement(); 
        ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1"); 
        while(rs.next()){
            System.out.println("name: "+rs.getString("name")+" 年龄:"+rs.getInt("age"));
        }
    }
}

說明:

資料庫驅動:

Class.forName("com.mysql.jdbc.Driver");

取得連線:

Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);

建立

StatementPreparedStatement物件:

Statement stmt = conn.createStatement();

执行sql数据库查询:

ResultSet rs = stmt.executeQuery("SELECT id, name, age FROM m_user where id =1");

解析结果集:

System.out.println("name: "+rs.getString("name")+" 年龄:"+rs.getInt("age"));

在使用的时候,业务处理完成后记得关闭相关资源

使用过JDCB的朋友都知道,JDBC如果用到我们项目中基本上都会存在以下几个问题:

传统JDBC的问题

  • 创建数据库的连接存在大量的硬编码,
  • 执行statement时存在硬编码.
  • 频繁的开启和关闭数据库连接,会严重影响数据库的性能,浪费数据库的资源.
  • 存在大量的重复性编码

针对上面这些问题,于是一大堆持久化框架应运而生。

持久化框

做持久层的框架有很多,有orm系和utils系列。

  • orm系列的代表有:hibernateeclipseLinktopLink;

######### ##########utils###系列的代表有:###MyBatis###,###dbUtils###,###jdbcTemplate###等;####### ######下面對於這些框架做個簡單概述:######至於jpa,它只是一個規範標準,並非具體框架,不等同於spring-data-jpa;同時spring-data-jpa 也不是jpa 的具體實現,它只是jpa 規範標準的進一步封裝。 hibernate 是 jpa 最常見的實作框架,當然其他還有 eclipseLink,topLink。 ######MyBatis 的特點是在對 SQL 最佳化時,複雜 SQL 的最佳化可控性高,框架內部呼叫層次簡單,除了部分可以自動產生程式碼,還會有許多 SQL 需要自行編碼。 spring-data-jpa(hibernate) 的特點是在開發過程中,脫離 SQL 編碼開發,當然也支援本機SQL來查詢,框架內部呼叫層次複雜。 以上就可以根據實際的業務進度和業務支撐情況做出選擇了。 #########其實可以在一個專案同時支援 MyBatis 和 spring-data-jpa,複雜SQL走 mybatis,常用SQL走 spring-data-jpa。 #########鑑於前實際開發​​中使用數量,我們選擇###MyBatis### 進行分析。 ###

mybatis

新加入開發的朋友,估計不知道MyBatis 的前身,在2010年之前,不交MyBatis ,叫做ibatis

MyBatis 是一款優秀的持久性層框架,它支援客製化 SQL、預存程序以及進階映射。 MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定參數以及取得結果集。 MyBatis 可以使用簡單的XML 或註解來配置和映射原生訊息,將介面和Java 的POJOs(Plain Ordinary Java Object,普通的Java物件)映射成資料庫中的記錄。

特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:MyBatis 不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

案例

需要来源两个jar包:MyBatis的jar包和MySQL数据库连接jar包。

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>8.0.16</version>
</dependency>

创建一个表t_user(数据库也是肯定要自己创建的哈)

 CREATE TABLE `t_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

插入一条数据:

INSERT INTO `t_user` VALUES (&#39;1&#39;, &#39;tian&#39;, &#39;19&#39;, &#39;1&#39;);

创建该数据库表的实体类:

public class User {
    private Integer id;
    private String name;
    private Integer age;
    //set get
}

创建mapper配置文件:UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tian.mapper.UserMapper">
    <select id="selectUserById" resultType="com.tian.domain.User">
        select * from t_user where id = #{id}
    </select>
</mapper>

创建mapper接口:UserMapper.java

import com.tian.domain.User;

public interface UserMapper {
    User selectUserById(Integer id);
}

MyBatis 整体配置文件:

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mblog?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;useSSL=false&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

上面这些就是我们使用MyBatis基本开发代码。

下面我们来写一个测试类:

import com.tian.domain.User;
import com.tian.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MybatisApplication {

    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        SqlSession sqlSession =null;
        try {
            //读取配置文件
            inputStream = Resources.getResourceAsStream(resource);
            //创建SqlSession工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            //创建sql操作会话
            sqlSession = sqlSessionFactory.openSession();
            UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
            //获取数据并解析成User对象
            User user = userMapper.selectUserById(1);
            //输出
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭相关资源
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            sqlSession.close();
        }
    }

}

测试结果:

User{id=1, name='tian', age=19}

如上面的代码所示,SqlSession是MyBatis中提供的与数据库交互的接口,SqlSession实例通过工厂模式创建。

为了创建SqlSession对象,首先需要创建SqlSessionFactory对象,而SqlSessionFactory对象的创建依赖于SqlSessionFactoryBuilder类,该类提供了一系列重载的build()方法,我们需要以主配置文件的输入流作为参数调用SqlSessionFactoryBuilder对象的bulid()方法,该方法返回一个SqlSessionFactory对象。

有了SqlSessionFactory物件之後,呼叫SqlSessionFactory物件的openSession()方法即可取得一個與資料庫建立連線的SqlSession實例。

前面我們定義了UserMapper接口,這裡需要呼叫SqlSessiongetMapper()方法建立一個動態代理對象,然後呼叫UserMapper代理實例的方法即可完成與資料庫的互動。

針對上面這個案例,我們來整理一下MyBatis的整體執行流程和核心元件。

MyBatis 核心元件

#MyBatis 執行流程

一週學完MyBatis源碼,萬字總結


Configuration

#用於描述MyBatis的主配置訊息,其他元件需要取得設定資訊時,直接透過Configuration物件取得。除此之外,MyBatis在應用程式啟動時,將Mapper配置資訊、型別別名、TypeHandler等註冊到Configuration元件中,其他元件需要這些資訊時,也可以從Configuration物件中取得。

MappedStatement

MappedStatement用於描述Mapper中的SQL配置訊息,是對Mapper XML設定檔中b5d5a6a8d1a81baf21ea188537c0af35等標籤或@Select/@Update等註解配置資訊的封裝。

SqlSession

#SqlSession是MyBatis提供的使用者導向的API,表示和資料庫互動時的會話對象,用於完成資料庫的增刪改查功能。 SqlSession是Executor元件的外觀,目的是對外提供易於理解和使用的資料庫操作介面。

Executor

Executor是MyBatis的SQL執行器,MyBatis中資料庫所有的增刪改查運算都是由Executor組件完成的。

StatementHandler

StatementHandler封裝了對JDBC Statement物件的操作,例如為Statement物件設定參數,呼叫Statement介面提供的方法與資料庫交互,等等。

ParameterHandler

MyBatis框架所使用的Statement類型為CallableStatement PreparedStatement時,ParameterHandler用於為Statement物件參數佔位符設定值。

ResultSetHandler

#ResultSetHandler封裝了對JDBC中的ResultSet物件操作,執行SQL類型為SELECT語句時,ResultSetHandler用於將查詢結果轉換成Java物件。

TypeHandler

TypeHandler是MyBatis中的類型處理器,用於處理Java類型與JDBC類型之間的對應。它的作用主要體現在能夠根據Java類型呼叫PreparedStatementCallableStatement物件對應的setXXX()方法為Statement物件設定值,而且能夠根據Java類型呼叫ResultSet物件對應的getXXX()取得SQL執行結果。

使用JDBC API開發應用程序,其中一個比較煩瑣的環節是處理JDBC類型與Java類型之間的轉換。涉及Java類型和JDBC型別轉換的兩種情況如下:

  • PreparedStatement物件為參數佔位符設定值時,需要呼叫PreparedStatement介面中提供的一系列的setXXX()方法,將Java類型轉換為對應的JDBC類型並為參數佔位符賦值。
  • 執行SQL語句取得ResultSet物件後,需要呼叫ResultSet物件的getXXX()方法取得欄位值,此時會將JDBC類型轉換為Java類型。

MyBatis提供的TypeHandler#及與Java類型和JDBC類型之間的對應關係:

一週學完MyBatis源碼,萬字總結

小結

我們使用到了SqlSession元件,它是使用者層面的API。實際上SqlSession是Executor元件的外觀,目的是為使用者提供更友善的資料庫操作接口,這是設計模式中外觀模式的典型應用。

真正執行SQL操作的是Executor元件,Executor可以理解為SQL執行器,它會使用StatementHandler元件對JDBC的Statement物件進行操作。

當Statement類型為CallableStatementPreparedStatement時,會透過ParameterHandler元件為參數佔位符賦值。 ParameterHandler元件中會根據Java類型找到對應的TypeHandler對象,TypeHandler中會透過Statement物件提供的setXXX()方法(例如setString()方法)為Statement物件中的參數佔位符設定值。

StatementHandler元件使用JDBC中的Statement物件與資料庫完成互動後,當SQL語句類型為SELECT時,MyBatis透過ResultSetHandler#元件從Statement物件中取得ResultSet對象,然後將ResultSet物件轉換為Java對象。

進階技能

設計模式

在MyBatis 中大量的使用了設計模式,在MyBatis 我們可以學到幾個設計模式:

  • 工廠模式
  • #範本方法模式
  • 代理模式
  • 建造者模式
  • 單例模式
  • #適應模式
  • 裝飾模式
  • 責任鏈模式
  • ....

##快取

在Web 應用程式中,快取是不可或缺的元件。通常我們都會用 Redis 或 memcached 等快取中間件,攔截大量奔向資料庫的請求,減輕資料庫壓力。作為一個重要的元件,MyBatis 自然也在內部提供了相應的支援。透過在框架層面增加快取功能,可減輕資料庫的壓力,同時又可以提升查詢速度,可謂一舉兩得。

MyBatis 快取結構由

一級快取二級快取所構成,這兩層快取都是使用 Cache 介面的實作類別。因此,在接下裡的章節中,我會先向大家介紹 Cache 幾種實作類別的源碼,然後再分析一級和二級快取的實作。

MyBatis一級快取和二級快取的使用:MyBatis一級快取是SqlSession等級的緩存,預設是開啟的,而且無法關閉;二級快取需要在MyBatis主設定檔中透過設定cacheEnabled參數值來開啟。

一級快取是在Executor中實現的。 MyBatis的Executor元件有3種不同的實現,分別為SimpleExecutorReuseExecutorBatchExecutor。這些類別都繼承自BaseExecutor,在BaseExecutor類別的query()方法中,首先從快取中取得查詢結果,如果取得不到,則從資料庫中查詢結果,然後將查詢結果快取起來。而MyBatis的二級緩存則是透過裝飾器模式實現的,當透過cacheEnabled參數開啟了二級緩存,MyBatis框架會使用CachingExecutor對SimpleExecutorReuseExecutor BatchExecutor進行裝飾,當執行查詢操作時,對查詢結果進行緩存,執行更新操作時則更新二級快取。本章最後介紹了MyBatis如何整合Redis作為二級快取。

除此之外,MyBatis也支援EhcacheOSCache等,這種特性並不常用。

插件

大多數框架,都支援插件,使用者可透過編寫插件來自行擴充功能,Mybatis也不例外。

MyBatis提供了擴充機制,能夠在執行Mapper時改變SQL的執行行為。這種擴展機制是透過攔截器來實現的,用戶自訂的攔截器也被稱為MyBatis插件。

MyBatis框架支援對ExecutorParameterHandlerResultSetHandlerStatementHandler四種元件的方法進行攔截。掌握了MyBatis插件的實作原理,然後自己實作一個分頁查詢外掛程式和慢SQL統計插件,當我們需要的功能MyBatis框架無法滿足時,可以考慮透過自訂外掛程式來實現。

經典實作:PageHelper 。

日誌

MyBatis針對不同的日誌框架提供對Log介面對應的實現,Log介面的實作類別下圖所示。從實作類別可以看出,MyBatis支援7種不同的日誌實現,具體如下。

一週學完MyBatis源碼,萬字總結

下面對常用幾個做一個簡單說明:

  • Apache Commons Logging:使用JCL輸出日誌。
  • Log4j 2:使用Log4j 2框架輸入日誌。
  • Java Util Logging:使用JDK內建的日誌模組輸出日誌。
  • Log4j:使用Log4j框架輸出日誌。
  • No Logging:不輸出任何日誌。
  • SLF4J:使用SLF4J日誌門面輸出日誌。
  • Stdout:將日誌輸出到標準輸出裝置(例如控制台)。

MyBatis找出日誌框架的順序為

SLF4J→JCL→Log4j2→Log4j→JUL→No Logging。

如果Classpath下不存在任何日誌框架,則使用NoLoggingImpl日誌實作類,即不輸出任何日誌。

動態SQL綁定

動態SQL指的是事先無法預知特定的條件,需要在執行時間根據具體的情況動態地產生SQL語句。

在MyBatis中有著豐富的動態SQL標籤,例如:196185dae55b7edbe154a5051db664a7f3bf5eff46860b27119c8dd4e92f1e57e20de2930bec9fdf236d6074634c0825等。

在MyBatis原始碼中有個SqlSource物件會作為MappedStatement物件的屬性保存在MappedStatement物件中。執行Mapper時,會根據傳入的參數資訊呼叫SqlSource物件的getBoundSql()方法取得BoundSql對象,這個過程就完成了將SqlNode物件轉換為SQL語句的過程。

例如:,#{}佔位符會被替換為“?”,然後呼叫JDBC中PreparedStatement物件的setXXX()方法為參數佔位符設定值,而$ {}佔位符則會直接替換為傳入的參數文字內容。

總結

#本文是對MyBatis原始碼分析的整體感受,希望對你有點幫助。

#

以上是一週學完MyBatis源碼,萬字總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:Java后端技术全栈。如有侵權,請聯絡admin@php.cn刪除