day02-自己實現Mybatis底層機制-01

来源:https://www.cnblogs.com/liyuelian/archive/2023/02/23/17149769.html
-Advertisement-
Play Games

自己實現Mybatis底層機制-01 主要實現:封裝SqlSession到執行器+Mapper介面和Mapper.xml+MapperBean+動態代理Mapper的方法 1.Mybatis整體架構分析 對上圖的解讀: 1)mybatis 的核心配置文件 ​ mybatis-config.xml:進 ...


自己實現Mybatis底層機制-01

主要實現:封裝SqlSession到執行器+Mapper介面和Mapper.xml+MapperBean+動態代理Mapper的方法

1.Mybatis整體架構分析

對上圖的解讀:

1)mybatis 的核心配置文件

​ mybatis-config.xml:進行全局配置,全局只能有一個這樣的配置文件

​ XxxMapper.xml 配置多個SQL,可以有多個 XxxMapper.xml 配置文件

2)通過 mybatis-config.xml 配置文件得到 SqlSessionFactory

3)通過 SqlSessionFactory 得到 SqlSession,用 SqlSession 就可以操作數據了

4)SqlSession 底層是 Executor(執行器),有兩個重要的實現類

image-20230222211659114

5)MappedStatement 是通過 XxxMapper.xml 來定義的,用來生成 statement 對象

6)參數輸入執行並輸出結果集,無需動手判斷參數類型和參數下標位置,且自動將結果集映射為Java對象

2.搭建開發環境

(1)創建maven項目

image-20230223184352888

(2)在pom.xml 中引入必要的依賴

<!--指定編譯器/source/target的版本-->
<properties>
    <project.build.sourdeEncoding>UTF-8</project.build.sourdeEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <java.version>1.8</java.version>
</properties>

<!--引入必要的依賴-->
<dependencies>
    <!--dom4j-->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.49</version>
    </dependency>
    <!--lombok-簡化entity/javabean/pojo 的開發-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.4</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

(3)創建資料庫和表

-- 創建資料庫
CREATE DATABASE `li_mybatis`;
USE `li_mybatis`;
-- 創建monster表
CREATE TABLE `monster`(
`id` INT NOT NULL AUTO_INCREMENT,
`age` INT NOT NULL,
`birthday` DATE DEFAULT NULL,
`email` VARCHAR(255) NOT NULL,
`gender` TINYINT NOT NULL,-- 1 male,0 female
`name` VARCHAR(255) NOT NULL,
`salary` DOUBLE NOT NULL,
PRIMARY KEY(`id`)
)CHARSET=utf8
-- insert
INSERT INTO `monster` VALUES(NULL,200,'2000-11-11','[email protected]',1,'牛魔王',8888);
image-20230223185346450

3.設計思路

image-20230223191810930

解讀:

  1. 傳統的方式操作資料庫
    1)得到 MySession 對象
    2)調用 MyExecutor 的方法完成操作
    3)MyExecutor 的連接是從 MyConfiguration 獲取

  2. Mybatis 操作資料庫的方式
    1)得到 MySession 對象
    2)不直接調用 MyExecutor 的方法
    3)而是通過 MyMapperProxy 獲取 Mapper 對象
    4)調用 Mapper 的方法,完成對資料庫的操作
    5)Mapper 最終還是動態代理方式,使用 MyExecutor 的方法完成操作
    6)這裡比較麻煩的就是 MyMapperProxy 的動態代理機制如何實現

4.任務階段1

階段1任務:通過配置文件,獲取資料庫連接

4.1分析

image-20230223192425061

4.2代碼實現

(1)在src 的 resources目錄下創建 my-config.xml,模擬原生的 mybatis 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<database>
    <!--配置連接資料庫的信息-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/li_mybatis?
    useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</database>

(2)創建 MyConfiguration 類,用來讀取xml文件,建立連接

因為這裡重點是實現 Mybatis 的底層機制,為了簡化操作,就不使用資料庫連接池了,直接使用原生的connection 連接

package com.li.limybatis.sqlsession;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;

/**
 * @author 李
 * @version 1.0
 * 用來讀取xml文件,建立連接
 */
public class MyConfiguration {
    //屬性-類的載入器
    private static ClassLoader loader = ClassLoader.getSystemClassLoader();

    //讀取xml文件並處理
    public Connection build(String resource) {
        Connection connection = null;
        try {
            //先載入配置文件 my-config.xml,獲取對應的InputStream
            InputStream stream = loader.getResourceAsStream(resource);
            //解析 my-config.xml文件
            SAXReader reader = new SAXReader();
            Document document = reader.read(stream);
            //獲取 xml文件的根元素 <database>
            Element root = document.getRootElement();
            System.out.println("root=" + root);
            //根據root解析,獲取Connection
            connection = evalDataSource(root);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }

    //解析 my-config.xml 的信息,並返回 Connection
    private Connection evalDataSource(Element node) {
        if (!"database".equals(node.getName())) {
            throw new RuntimeException("root節點應該是<database>");
        }

        //連接DB的必要參數
        String driverClassName = null;
        String url = null;
        String username = null;
        String password = null;

        //遍歷node下的子節點,獲取其屬性值
        for (Object item : node.elements("property")) {
            //i就是對應的 property節點
            Element i = (Element) item;
            //property節點的 name屬性的值
            String name = i.attributeValue("name");
            //property節點的 value屬性的值
            String value = i.attributeValue("value");

            //判斷值是否為空
            if (name == null || value == null) {
                throw new RuntimeException("property節點沒有設置name或value屬性!");
            }
            switch (name) {
                case "url":
                    url = value;
                    break;
                case "username":
                    username = value;
                    break;
                case "driverClassName":
                    driverClassName = value;
                    break;
                case "password":
                    password = value;
                    break;
                default:
                    throw new RuntimeException("屬性名沒有匹配到..");
            }
        }
        //獲取連接
        Connection connection = null;
        try {
            Class.forName(driverClassName);
            connection = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return connection;
    }
}

5.任務階段2

階段2任務:通過實現執行器機制,對數據表進行操作

5.1分析

我們把對資料庫的操作封裝到一套Executor機制中,程式具有更好的拓展性,結構更加清晰。這裡我們先實現傳統的方式連接資料庫,即通過MyExecutor直接操作資料庫。

image-20230223202152737

5.2代碼實現

(1)生成 entity 類 Monster.java

package com.li.entity;

import lombok.*;

import java.util.Date;

/**
 * @author 李
 * @version 1.0
 * Monster類和 monster有映射關係
 *
 * 註解說明:
 * @Getter 給所有屬性生成 getter方法
 * @Setter 給所有屬性生成 setter方法
 * @ToString 生成toString方法
 * @NoArgsConstructor 生成一個無參構造器
 * @AllArgsConstructor 生成一個全參構造器
 * @Data 會生成上述除了無參/全參構造器的所有方法,此外還會生成equals,hashCode等方法
 */
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {
    
    private Integer id;
    private Integer age;
    private String name;
    private String email;
    private Date birthday;
    private double salary;
    private Integer gender;
}

(2)Executor 介面

package com.li.limybatis.sqlsession;

/**
 * @author 李
 * @version 1.0
 */
public interface Executor {
    //泛型方法
    public <T> T query(String statement, Object parameter);
}

(3)執行器實現類 MyExecutor.java

package com.li.limybatis.sqlsession;

import com.li.entity.Monster;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author 李
 * @version 1.0
 */
public class MyExecutor implements Executor {
    private MyConfiguration myConfiguration = new MyConfiguration();

    /**
     * 根據sql,返回查詢結果
     *
     * @param sql
     * @param parameter
     * @param <T>
     * @return
     */
    @Override
    public <T> T query(String sql, Object parameter) {
        //獲取連接對象
        Connection connection = getConnection();
        //查詢返回的結果集
        ResultSet set = null;
        PreparedStatement pre = null;
        try {
            //構建PreparedStatement對象
            pre = connection.prepareStatement(sql);
            //設置參數,如果參數多,可以使用數組處理
            pre.setString(1, parameter.toString());
            //查詢返回的結果集
            set = pre.executeQuery();
            //把結果集的數據封裝到對象中-monster
            //說明:這裡做了簡化處理,認為返回的結果就是一個monster記錄,完善的寫法應該使用反射機制
            Monster monster = new Monster();
            //遍歷結果集,將數據封裝到monster對象中
            while (set.next()) {
                monster.setId(set.getInt("id"));
                monster.setName(set.getString("name"));
                monster.setEmail(set.getString("email"));
                monster.setAge(set.getInt("age"));
                monster.setGender(set.getInt("gender"));
                monster.setBirthday(set.getDate("birthday"));
                monster.setSalary(set.getDouble("salary"));
            }
            return (T) monster;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (set != null) {
                    set.close();
                }
                if (pre != null) {
                    pre.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    //編寫方法,通過myConfiguration對象返回連接
    private Connection getConnection() {
        Connection connection = myConfiguration.build("my-config.xml");
        return connection;
    }
}

(4)進行測試

@Test
public void query() {
    Executor executor = new MyExecutor();
    Monster monster =
            (Monster) executor.query("select * from monster where id = ?", 1);
    System.out.println("monster--" + monster);
}

測試結果:

image-20230223211725700

6.任務階段3

階段3任務:將執行器封裝到SqlSession

6.1代碼實現

(1)創建 MySqlSession 類,將執行器封裝到SqlSession中。

package com.li.limybatis.sqlsession;

/**
 * @author 李
 * @version 1.0
 * MySqlSession:搭建Configuration(連接)和Executor之間的橋梁
 */
public class MySqlSession {
    //執行器
    private Executor executor = new MyExecutor();
    //配置
    private MyConfiguration myConfiguration = new MyConfiguration();

    //編寫方法selectOne,返回一條記錄
    public <T> T selectOne(String statement,Object parameter){
        return executor.query(statement, parameter);
    }
}

(2)測試

@Test
public void selectOne() {
    MySqlSession mySqlSession = new MySqlSession();
    Monster monster =
            (Monster) mySqlSession.selectOne("select * from monster where id=?", 1);
    System.out.println("monster=" + monster);
}

測試結果:

image-20230223223059776
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 JavaScript 原型是該語言中一個非常重要的概念。理解原型是理解 JavaScript 的關鍵。在本篇技術博客中,我們將深入探討 JavaScript 的原型概念,並介紹常用的操作對象原型的方法。(歡迎點評,歡迎指正!) 什麼是原型? 在 JavaScript 中,每個對象都有一個原型( ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 npm 是 node 捆綁的依賴管理器,常用程度可想而知。那麼你每天都在 npm/yarn run 的命令到底是如何運行項目的呢? 前端項目中運行 npm run xxx 的時候發生了什麼?大家都知道目前的 node 是捆綁 npm 的。 ...
  • 一、常規 在 JavaScript 中,apply、call、bind 是三個與函數調用相關的方法,它們都允許你在調用函數時手動設置函數的上下文(即 this 指向)。 1、apply 方法:apply 方法允許你調用一個函數,並且手動設置函數的上下文(即 this 指向)以及傳遞一個參數數組。其語 ...
  • 本文是系列第三篇。系列文章: 現代圖片性能優化及體驗優化指南 - 圖片類型及 Picture 標簽的使用 現代圖片性能優化及體驗優化指南 - 響應式圖片方案 圖片資源,在我們的業務中可謂是占據了非常大頭的一環,尤其是其對帶寬的消耗是十分巨大的。 對圖片的性能優化及體驗優化在今天就顯得尤為重要。本文, ...
  • 一、直接在 el-table-column 外嵌套 el-form 符合表單的校驗習慣,唯一需要註意的地方 el-form 需要綁定 :model="scope.row" // html <div id="app"> <el-table :data="list" border> <el-table- ...
  • 架構的核心是管理複雜度,架構師的核心能力是抽象能力,什麼是抽象能力?抽象能力就是一種化繁為簡的能力。何為化繁為簡?就是把一種複雜的事情變得簡單的能力,比如通過打比喻讓別人很容易聽明白你說的意思就是一種抽象能力。如何鍛煉抽象能力?我覺得有三種方法,第一種是用歸納法找共性,從多個問題中找到共同的問題提煉... ...
  • 1. Scala 1.1. 混合了面向對象和函數式編程的語言 1.2. 直接使用任何一個Java類庫 1.3. 聲明非遞歸的方法時,不需要顯式地返回類型 1.3.1. 會自動地替你推斷生成一個 1.4. 字元串插值 1.4.1. 在字元串的字面量中內嵌變數和表達式 1.5. 以使用val關鍵字替換v ...
  • 說起辦公自動化就離不開對excel表格的處理,現在哪個辦公族不得每天面對著幾份excel過日本子?本次活動同時在稀土掘金、博客園、微信公眾號舉辦,互不幹擾,歡迎各平臺搜索博主 ENG八戒 參與最新活動 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...