一文全瞭解Mybatis

来源:https://www.cnblogs.com/jiuxialb/archive/2023/05/22/17420775.html
-Advertisement-
Play Games

## 初步瞭解 ### 總體架構設計 Mybatis 整體框架如下: ![img](https://zhangjiahao-blog.oss-cn-beijing.aliyuncs.com/picgo/202305161021323.png) ##### 介面層 MyBatis 和資料庫的交互有兩種 ...


初步瞭解

總體架構設計

Mybatis 整體框架如下:

img

介面層

MyBatis 和資料庫的交互有兩種方式:

  • 使用傳統的 MyBatis 提供的 API;
  • 使用 Mapper 介面;
使用傳統的 MyBatis 提供的 API

這是傳統的傳遞 Statement Id 和查詢參數給 SqlSession 對象,使用 SqlSession 對象完成和資料庫的交互;MyBatis 提供了非常方便和簡單的 API,供用戶實現對資料庫的增刪改查數據操作,以及對資料庫連接信息和 MyBatis 自身配置信息的維護操作。

img

使用 Mapper 介面

MyBatis 將配置文件中的每一個<mapper> 節點抽象為一個 Mapper 介面,而這個介面中聲明的方法和跟<mapper> 節點中的<select|update|delete|insert> 節點項對應,即<select|update|delete|insert> 節點的 id 值為 Mapper 介面中的方法名稱,parameterType 值表示 Mapper 對應方法的入參類型,而 resultMap 值則對應了 Mapper 介面表示的返回值類型或者返回結果集的元素類型。

img

根據 MyBatis 的配置規範配置好後,通過 SqlSession.getMapper(XXXMapper.class)方法,MyBatis 會根據相應的介面聲明的方法信息,通過動態代理機制生成一個 Mapper 實例,我們使用 Mapper 介面的某一個方法時,MyBatis 會根據這個方法的方法名和參數類型,確定 Statement Id,底層還是通過 SqlSession.select("statementId",parameterObject);或者 SqlSession.update("statementId",parameterObject); 等等來實現對資料庫的操作, MyBatis 引用 Mapper 介面這種調用方式,純粹是為了滿足面向介面編程的需要。(其實還有一個原因是在於,面向介面的編程,使得用戶在介面上可以使用註解來配置 SQL 語句,這樣就可以脫離 XML 配置文件,實現“0 配置”)。

數據處理層

數據處理層可以說是 MyBatis 的核心,從大的方面上講,它要完成兩個功能:

  • 通過傳入參數構建動態 SQL 語句;
  • SQL 語句的執行以及封裝查詢結果集成List<E>
通過傳入參數構建動態 SQL 語句;

動態語句生成可以說是 MyBatis 框架非常優雅的一個設計,MyBatis 通過傳入的參數值,使用 Ognl 來動態地構造 SQL 語句,使得 MyBatis 有很強的靈活性和擴展性。

參數映射指的是對於 java 數據類型和 jdbc 數據類型之間的轉換:這裡有包括兩個過程:查詢階段,我們要將 java 類型的數據,轉換成 jdbc 類型的數據,通過 preparedStatement.setXXX() 來設值;另一個就是對 resultset 查詢結果集的 jdbcType 數據轉換成 java 數據類型。

SQL 語句的執行以及封裝查詢結果集成List<E>

動態 SQL 語句生成之後,MyBatis 將執行 SQL 語句,並將可能返回的結果集轉換成List<E> 列表。MyBatis 在對結果集的處理中,支持結果集關係一對多和多對一的轉換,並且有兩種支持方式,一種為嵌套查詢語句的查詢,還有一種是嵌套結果集的查詢。

框架支撐層
  • 事務管理機制

事務管理機制對於 ORM 框架而言是不可缺少的一部分,事務管理機制的質量也是考量一個 ORM 框架是否優秀的一個標準。

  • 連接池管理機制

由於創建一個資料庫連接所占用的資源比較大, 對於數據吞吐量大和訪問量非常大的應用而言,連接池的設計就顯得非常重要。

  • 緩存機制

為了提高數據利用率和減小伺服器和資料庫的壓力,MyBatis 會對於一些查詢提供會話級別的數據緩存,會將對某一次查詢,放置到 SqlSession 中,在允許的時間間隔內,對於完全相同的查詢,MyBatis 會直接將緩存結果返回給用戶,而不用再到資料庫中查找。

  • SQL 語句的配置方式

傳統的 MyBatis 配置 SQL 語句方式就是使用 XML 文件進行配置的,但是這種方式不能很好地支持面向介面編程的理念,為了支持面向介面的編程,MyBatis 引入了 Mapper 介面的概念,面向介面的引入,對使用註解來配置 SQL 語句成為可能,用戶只需要在介面上添加必要的註解即可,不用再去配置 XML 文件了,但是,目前的 MyBatis 只是對註解配置 SQL 語句提供了有限的支持,某些高級功能還是要依賴 XML 配置文件配置 SQL 語句。

引導層

引導層是配置和啟動 MyBatis 配置信息的方式。MyBatis 提供兩種方式來引導 MyBatis :基於 XML 配置文件的方式和基於 Java API 的方式。

主要構件及其相互關係

img

主要的核心部件解釋如下:

  • SqlSession 作為 MyBatis 工作的主要頂層 API,表示和資料庫交互的會話,完成必要資料庫增刪改查功能
  • Executor MyBatis 執行器,是 MyBatis 調度的核心,負責 SQL 語句的生成和查詢緩存的維護
  • StatementHandler 封裝了 JDBC Statement 操作,負責對 JDBC statement 的操作,如設置參數、將 Statement 結果集轉換成 List 集合。
  • ParameterHandler 負責對用戶傳遞的參數轉換成 JDBC Statement 所需要的參數,
  • ResultSetHandler 負責將 JDBC 返回的 ResultSet 結果集對象轉換成 List 類型的集合;
  • TypeHandler 負責 java 數據類型和 jdbc 數據類型之間的映射和轉換
  • MappedStatement MappedStatement 維護了一條<select|update|delete|insert>節點的封裝,
  • SqlSource 負責根據用戶傳遞的 parameterObject,動態地生成 SQL 語句,將信息封裝到 BoundSql 對象中,並返回
  • BoundSql 表示動態生成的 SQL 語句以及相應的參數信息
  • Configuration MyBatis 所有的配置信息都維持在 Configuration 對象之中。

流程簡解

準備

  1. /src/main/resources/mybatis-config.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>
    <properties>
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF8&amp;useSSL=false&amp;autoReconnect=true"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>

    <!-- 環境,可以配置多個,default:指定採用哪個環境 -->
    <environments default="test">
        <environment id="test">
            <!-- 事務管理器,JDBC類型的事務管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 數據源,池類型的數據源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/> <!-- 配置了properties,所以可以直接引用 -->
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="TeacherMapper.xml"/>
    </mappers>
</configuration>

  1. /src/main/resources/TeacherMapper.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:命名空間,隨便寫,一般保證命名空間唯一 -->
    <mapper namespace="TeacherMapper">
        <!-- statement,內容:sql語句。id:唯一標識,隨便寫,在同一個命名空間下保持唯一
           resultType:sql語句查詢結果集的封裝類型,tb_user即為資料庫中的表
         -->
        <select id="selectTest" resultType="org.apache.ibatis.test.Teacher">
            select *
            from teacher
            where id = #{id}
        </select>
    </mapper>
    
    
  2. /src/main/resources/log4j.properties

    log4j.rootLogger=DEBUG, stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
  3. /src/main/java/org/apache/ibatis/test/Teacher.java

    package org.apache.ibatis.test;
    
    
    public class Teacher {
        private Long id;
        private String name;
    
        public Long getId() {
            return id;
        }
     
        public void setId(Long id) {
            this.id = id;
        }
     
        public String getName() {
            return name;
        }
     
        public void setName(String name) {
            this.name = name;
        }
     
        @Override
        public String toString() {
            return "Teacher{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    '}';
        }
    }
    
    
  4. /src/main/java/org/apache/ibatis/test/Test.java

    package org.apache.ibatis.test;
    
    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 Test {
        public static void main(String[] args) throws IOException {
            // 指定全局配置文件
            String resource = "mybatis-config.xml";
            // 讀取配置文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 構建sqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            // 獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 操作CRUD,第一個參數:指定statement,規則:命名空間+“.”+statementId
            // 第二個參數:指定傳入sql的參數:這裡是用戶id
            Teacher test = sqlSession.selectOne("TeacherMapper.selectTest", 1);
            System.out.println(test.getName());
        }
    }
    

整體流程

  1. 通過 mybatis-config.xml 進行初始化創建出 SqlSessionFactory。其內部是通過創建XMLConfigBuilder對象,然後自己進行解析 XML文件,把文件內容解析封裝為Configuration對象,最後由SqlSessionFactory進行封裝為SqlSessionFactory對象來完成SqlSessionFactory的創建。
  2. 通過SqlSessionFactory對象進行開啟一個SqlSession。其內部是通過獲取ConfigurationEnvironment數據進行構建TransactionFactory–>Executor,最後封裝為SqlSession返回進行使用
  3. 通過SqlSession執行 XML 中對應的方法。其中是先通過statementXML 指定的類#方法名稱獲取MappedStatement對象,然後提交給Executor進行執行。

目錄詳解

exception

下圖是Mybatis中異常的關係圖:

image-20230522142059506

背景知識

  • 工廠模式
  • 異常的封裝

講解

工廠模式

public class ExceptionFactory {

    private ExceptionFactory() {
        // Prevent Instantiation
    }

    public static RuntimeException wrapException(String message, Exception e) {
        return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);
    }

}
  • 私有構造函數:導致該工廠無法創建出對應的對象
  • 靜態wrapException方法,用於通過異常信息和異常類型進行封裝異常為Mybatis中的異常類型。全局通過ExceptionFactory.wrapException()進行生產出對應的異常對象

異常類型

  • IbatisException:Mybatis中最高的異常,但是直接繼承該類的子類只有PersistenceException,而且該類也添加了@Deprecated說明以後可能去除。

  • PersistenceException: 譯為持久化異常。Mybatis對應是持久化框架,後期可能該異常類型為Mybatis所有異常的父類。

  • TooManyResultsException:譯為多條返回結果異常。用處為selectOne卻返回多條記錄時所拋出的異常。

  • TypeException: 譯為類型異常。當 Mybatis 中需要類型轉化時,若轉換失敗則會拋出該異常。

  • CacheException: 譯為緩存異常。當Mybatis讀取緩存中數據出現問題時則會拋出該異常。

  • ParsingException: 譯為解析異常。當前代碼未看到使用。

  • ScriptingException: 譯為腳本異常。

  • ResultMapException: 譯為結果映射異常。在結果轉換為對應類型的對象時,若轉換失敗則會拋出異常。

  • DataSourceException: 譯為數據源異常。在初始化數據源時若出現錯誤則會拋出該異常。

  • TransactionException: 譯為事務異常。在給connection開啟事務時若失敗則會拋出該異常。

  • BuilderException: 譯為建造異常。在建造對象失敗時會拋出該異常。

  • SqlSessionException: 譯為SqlSession的異常。基本只會在SqlSessionManager中使用,主要是SqlSession使用過程中的異常。

  • ReflectionException: 譯為反射異常。基本只會在反射使用時會拋出該異常。

  • ExecutorException: 譯為執行器異常。會線上程操作資料庫的時候拋出該異常。

  • BatchExecutorException:譯為批量執行器異常。會線上程批量操作資料庫的時候拋出該異常。

  • BindingException: 譯為綁定異常。主要是 mapper映射的時候會拋出該異常。

  • LogException: 譯為日誌異常。目前只在LogFactory構建日誌相關的時候才會拋出該異常。

  • PluginException: 譯為插件異常。目前只在Plugin中使用,在獲取插件信息時候會拋出該異常。

Mybatis類型主要是根據業務相關包放在一起,所以命名絕大多數都能夠直觀的看到原因所在。

jdbc

背景知識

  • 模版模式
  • 易用性

講解

模版模式

某些類通用的一些處理方法一致,但是處理對象可能存在不同,此時可以使用模版方法,抽取父類編寫通用處理方法,子類只需實現獲取對象的方法即可。

public class SQL extends AbstractSQL<SQL> {

    @Override
    public SQL getSelf() {
        return this;
    }

}
public abstract class AbstractSQL<T> {

    private static final String AND = ") \nAND (";
    private static final String OR = ") \nOR (";

    private final SQLStatement sql = new SQLStatement();

    public abstract T getSelf();

    public T UPDATE(String table) {
        sql().statementType = SQLStatement.StatementType.UPDATE;
        sql().tables.add(table);
        return getSelf();
    }

    public T SET(String sets) {
        sql().sets.add(sets);
        return getSelf();
    }
		......
}

以上代碼可知:

  1. 若用戶需要自定SQLExplainSQL,從而進行性能調優,此時只需要繼承 AbstractSQL即可,而無需編寫原方法。
易用性

為了用戶使用方便和構建 SQL的直觀性,AbstractSQL命名採用了全大寫的模式,以此讓用戶更加易用。

image-20230522152141832

SqlRunner
    public int insert(String sql, Object... args) throws SQLException {
        PreparedStatement ps;
        if (useGeneratedKeySupport) {
            ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
        } else {
            ps = connection.prepareStatement(sql);
        }

        try {
            setParameters(ps, args);
            ps.executeUpdate();
            if (useGeneratedKeySupport) {
                List<Map<String, Object>> keys = getResults(ps.getGeneratedKeys());
                if (keys.size() == 1) {
                    Map<String, Object> key = keys.get(0);
                    Iterator<Object> i = key.values().iterator();
                    if (i.hasNext()) {
                        Object genkey = i.next();
                        if (genkey != null) {
                            try {
                                return Integer.parseInt(genkey.toString());
                            } catch (NumberFormatException e) {
                                //ignore, no numeric key support
                            }
                        }
                    }
                }
            }
            return NO_GENERATED_KEY;
        } finally {
            try {
                ps.close();
            } catch (SQLException e) {
                //ignore
            }
        }
    }

    public int update(String sql, Object... args) throws SQLException {
        PreparedStatement ps = connection.prepareStatement(sql);
        try {
            setParameters(ps, args);
            return ps.executeUpdate();
        } finally {
            try {
                ps.close();
            } catch (SQLException e) {
                //ignore
            }
        }
    }

此類在Mybatis中沒有任何的使用,此類應該只是為了提供給用戶,讓用戶可以自定義執行相關 SQL,分析其方法本質為原始JDBC相關操作流程。

ScriptRunner
   private void executeFullScript(Reader reader) {
        StringBuilder script = new StringBuilder();
        try {
            BufferedReader lineReader = new BufferedReader(reader);
            String line;
            while ((line = lineReader.readLine()) != null) {
                script.append(line);
                script.append(LINE_SEPARATOR);
            }
            String command = script.toString();
            println(command);
            executeStatement(command);
            commitConnection();
        } catch (Exception e) {
            String message = "Error executing: " + script + ".  Cause: " + e;
            printlnError(message);
            throw new RuntimeSqlException(message, e);
        }
    }

    private void executeLineByLine(Reader reader) {
        StringBuilder command = new StringBuilder();
        try {
            BufferedReader lineReader = new BufferedReader(reader);
            String line;
            while ((line = lineReader.readLine()) != null) {
                handleLine(command, line);
            }
            commitConnection();
            checkForMissingLineTerminator(command);
        } catch (Exception e) {
            String message = "Error executing: " + command + ".  Cause: " + e;
            printlnError(message);
            throw new RuntimeSqlException(message, e);
        }
    }
    private void handleLine(StringBuilder command, String line) throws SQLException {
        String trimmedLine = line.trim();
        if (lineIsComment(trimmedLine)) {
            Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
            if (matcher.find()) {
                delimiter = matcher.group(5);
            }
            println(trimmedLine);
        } else if (commandReadyToExecute(trimmedLine)) {
            command.append(line.substring(0, line.lastIndexOf(delimiter)));
            command.append(LINE_SEPARATOR);
            println(command);
            executeStatement(command.toString());
            command.setLength(0);
        } else if (trimmedLine.length() > 0) {
            command.append(line);
            command.append(LINE_SEPARATOR);
        }
    }

此類的核心方法如上,可看出其本質和SqlRunner類一致。

datasource

背景知識

講解

transaction

背景知識

講解

cursor

背景知識

講解

io

背景知識

講解

reflection

背景知識

講解

logging

背景知識

講解

scripting

背景知識

講解

type

背景知識

講解

mapping

背景知識

講解

builder

背景知識

講解

parsing

背景知識

講解

binding

背景知識

講解

annotationslang

背景知識

講解

cache

背景知識

講解

plugin

背景知識

講解

session

背景知識

講解

流程詳解

總結

生活在失去所有的希望和期盼,才能一直做出納什均衡解
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 前言 Java 中的三元運算,平時也叫做三目運算,大家瞭解嗎?下麵就詳細介紹一下,以後在項目編程中用得到。 # 一、Java運算符 在最底層,Java 中的數據是通過使用運算符來操作的。運算符是一種特殊的符號,用來表示數據的運算、賦值和比較等等。每一種編程語言都有運算符,在 Java 中運算符可 ...
  • 視頻地址 # 1 Stream流概述 - 目的:簡化集合和數組操作的API,結合了Lambda表達式。 - Stream流式思想的核心: 1. 先得到集合或者數組的Stream流(就是一根傳送帶) 2. 把元素放上去 3. 用這個Stream流簡化的API來方便的操作元素 # 2 Stream流獲取 ...
  • ### 子類調用父類的方法的三種方式: - 父類名.方法名(self) - super(子類名,self).父類方法名() - super().父類方法名 註意:super()通過子類調用當前父類的方法,super預設會調用第一個父類的方法(適用於單繼承的多層繼承 如下代碼: ```python # ...
  • 話說兄弟們,女朋友生氣了都是怎麼哄的? 不會吧不會吧,不會有人還是單身狗吧! 算了,還是回到正題吧,再說我要挨打了~ 今天咱們來交流一下程式員是怎麼哄女朋友的,話不多說直接開始! 準備工作 1、環境 首先我們準備好環境和編輯器,我使用的是: Python 3.8 解釋器 Pycharm 編輯器 2、 ...
  • 雖然PDF文件適合用於列印和發佈,但不適合所有類型的文檔。例如,包含複雜圖表和圖形的文檔可能無法在PDF中呈現得很好。但是HTML文件可以在任何可運行瀏覽器的電腦上進行閱讀並顯示。並且HTML還具有占用伺服器資源較小,便於搜索引擎收錄的特點。那麼今天這篇文章就將展示如何通過Java應用程式將PDF ...
  • 你好!我是[@馬哥python說](https://www.zhihu.com/people/13273183132),一枚10年程式猿👨🏻‍💻,正在試錯用pyecharts開發可視化大屏的非常規排版。 以下,我用8種ThemeType展示的同一個可視化數據大屏。 **1、SHINE主題** ...
  • 前文我們說過了BIO,今天我們聊聊NIO。NIO 是什麼?NIO官方解釋它為New lO,由於其特性我們也稱之為,Non-Blocking IO。這是jdk1.4之後新增的一套IO標準。為什麼要用NIO呢?我們再簡單回顧下BIO:阻塞式IO,原理很簡單,其實就是多個端點與服務端進行通信時,每個客戶端 ...
  • # 前置需求 一臺電腦(我用的是Windows),有網 # 第一步:下載並安裝 去java官網下載開發版java(考慮到可能有小白,我暫且這麼說) java官網下載鏈接:https://www.oracle.com/java/technologies/downloads/ 寫隨筆時間為2023、05 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...