科普文:深入理解Mybatis

概敘

? ? ? ??
(1) JDBC

? ? ? ? JDBC(Java Data Base Connection,java數據庫連接)是一種用于執行SQL語句的Java API,可以為多種關系數據庫提供統一訪問,它由一組用Java語言編寫的類和接口組成.JDBC提供了一種基準,據此可以構建更高級的工具和接口,使數據庫開發人員能夠編寫數據庫應用程序。

????????·優點:運行期:快捷、高效

????????·缺點:編輯期:代碼量大、繁瑣異常處理、不支持數據庫跨平臺

(2) DBUtils(相當于C#中的DBHelper類)

? ? ? ? DBUtils是Java編程中的數據庫操作實用工具,小巧簡單實用。DBUtils封裝了對JDBC的操作,簡化了JDBC操作,可以少寫代碼。

????????DBUtils三個核心功能介紹

  1. ????????QueryRunner中提供對sql語句操作的API
  2. ????????ResultSetHandler接口,用于定義select操作后,怎樣封裝結果集
  3. ????????DBUtils類,它就是一個工具類,定義了關閉資源與事務處理的方法

(3)Hibernate

????????Hibernate 是由 Gavin King 于 2001 年創建的開放源代碼的對象關系框架。它強大且高效的構建具有關系對象持久性和查詢服 務的 Java 應用程序。

????????Hibernate 將 Java 類映射到數據庫表中,從 Java 數據類型中映射到 SQL 數據類型中,并把開發人員從 95% 的公共數據持續 性編程工作中解放出來。

????????Hibernate 是傳統 Java 對象和數據庫服務器之間的橋梁,用來處理基于 O/R 映射機制和模式的那些對象。

Hibernate 優勢

  1. Hibernate 使用 XML 文件來處理映射 Java 類別到數據庫表格中,并且不用編寫任何代碼
  2. 為在數據庫中直接儲存和檢索 Java 對象提供簡單的 APIs。
  3. 如果在數據庫中或任何其它表格中出現變化,那么僅需要改變 XML 文件屬性。
  4. 抽象不熟悉的 SQL 類型,并為我們提供工作中所熟悉的 Java 對象。
  5. Hibernate 不需要應用程序服務器來操作。
  6. 操控你數據庫中對象復雜的關聯。
  7. 最小化與訪問數據庫的智能提取策略。
  8. 提供簡單的數據詢問。

Hibernate劣勢

  1. hibernate的完全封裝導致無法使用數據的一些功能。
  2. Hibernate的緩存問題。
  3. Hibernate對于代碼的耦合度太高。
  4. Hibernate尋找bug困難。
  5. Hibernate批量數據操作需要大量的內存空間而且執行過程中需要的對象太多

(4) JDBCTemplate

????????JdbcTemplate針對數據查詢提供了多個重載的模板方法,你可以根據需要選用不同的模板方法.如果你的查詢很簡單,僅僅是傳入相應SQL或者相關參數,然后取得一個單一的結果,那么你可以選擇如下一組便利的模板方法。

????????優點:運行期:高效、內嵌Spring框架中、支持基于AOP的聲明式事務

?????????缺點:必須于Spring框架結合在一起使用、不支持數據庫跨平臺、默認沒有緩存

(5) MyBatis

????????MyBatis 是一款優秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。

????????1、Mybatis是一個半ORM(對象關系映射)框架,底層封裝了JDBC,是程序員在開發時只需要關注SQL語句本身,不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。使得程序員可以花更多的精力放到業務開發中。另外,程序員直接編寫原生態sql,嚴格控制sql執行性能,靈活度高。

????????2、MyBatis 可以使用簡單的 XML文件 或注解方式來配置和映射原生信息,將 POJO映射成數據庫中的記錄,避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。

????????3、通過xml 文件或注解的方式將要執行的各種 statement 配置起來,并通過java對象和 statement中sql的動態參數進行映射生成最終執行的sql語句,最后由mybatis框架執行sql并將結果映射為java對象并返回。(從執行sql到返回result的過程)。

Mybaits的優點:

????????1、基于SQL語句編程,相當靈活,不會對應用程序或者數據庫的現有設計造成任何影響,SQL寫在XML里,解除sql與程序代碼的耦合,便于統一管理;提供XML標簽,支持編寫動態SQL語句,并可重用。

????????2、與JDBC相比,減少了50%以上的代碼量,消除了JDBC大量冗余的代碼,不需要手動開關連接;

????????3、很好的與各種數據庫兼容(因為MyBatis使用JDBC來連接數據庫,所以只要JDBC支持的數據庫MyBatis都支持)。

????????4、能夠與Spring很好的集成;

????????5、提供映射標簽,支持對象與數據庫的ORM字段關系映射;提供對象關系映射標簽,支持對象關系組件維護。

MyBatis框架的缺點:

????????1、SQL語句編寫工作量大,熟練度要高:SQL語句的編寫工作量較大,尤其當字段多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。

????????2、SQL語句依賴于數據庫,導致數據庫移植性差,不能隨意更換數據庫。

MyBatis框架適用場合:

????????1、MyBatis專注于SQL本身,是一個足夠靈活的DAO層解決方案。

????????2、對性能的要求很高,或者需求變化較多的項目,如互聯網項目,MyBatis將是不錯的選擇。

MyBatis與Hibernate有哪些不同?

????????1、Mybatis是一個半自動的ORM框架,在查詢關聯對象或關聯集合對象時,需要手動編寫sql語句來完成;Hibernate是全自動ORM映射工具,查詢關聯對象或者關聯集合對象時,可以根據對象關系模型直接獲取,不需要編寫sql.

????????2、Mybatis直接編寫原生態sql,可以嚴格控制sql執行性能,靈活度高,非常適合對性能要求高,需求變化頻繁的項目;但是如果涉及到較多的字段或者關聯多表時,sql語句編寫量大且對開發人的sql語句編寫功底要求高。

????????3、Hibernate對象/關系映射能力強,數據庫無關性好,適合需求變化不大的項目,使用hibernate開發可以節省很多代碼,提高效率。

MyBatis 的核心組件

1、SqlSessionFactoryBuilder:

  • SqlSessionFactoryBuilder 負責解析配置文件并構建 SqlSessionFactory。

  • 它通常使用 XML 配置文件(mybatis-config.xml)作為輸入。

  • 在解析過程中,它會創建 Configuration 對象,該對象包含 MyBatis 的所有配置信息。

  • 解析完成后,它會調用 SqlSessionFactoryBuilder 的 build 方法來創建 SqlSessionFactory 實例。

SqlSessionFactoryBuilder?是 MyBatis 中用于構建?SqlSessionFactory?的類。它主要負責解析 MyBatis 的配置文件,并基于配置信息構建?SqlSessionFactory。由于 MyBatis 的源代碼文件通常較長,V哥盡量簡化并只列出與?SqlSessionFactoryBuilder?相關的關鍵代碼段,并加上注釋。

以下是?SqlSessionFactoryBuilder?的代碼簡化版本:

import?org.apache.ibatis.builder.xml.XMLConfigBuilder;??
import?org.apache.ibatis.session.SqlSessionFactory;??
import?org.apache.ibatis.session.SqlSessionFactoryBuilder;??import?java.io.InputStream;??
import?java.io.Reader;??public?class?SqlSessionFactoryBuilder?{??//?使用XML配置文件構建SqlSessionFactory??public?SqlSessionFactory?build(Reader?reader)?{??return?build(reader,?null,?null);??}??//?使用XML配置文件構建SqlSessionFactory,并允許傳入Environment和Properties??public?SqlSessionFactory?build(Reader?reader,?String?environment,?Properties?properties)?{??try?{??//?使用XML配置構建器創建Configuration對象??XMLConfigBuilder?parser?=?new?XMLConfigBuilder(reader,?environment,?properties);??//?解析配置文件,返回Configuration對象??Configuration?configuration?=?parser.parse();??//?基于Configuration對象創建SqlSessionFactory??return?new?SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration);??}?catch?(Exception?e)?{??throw?ExceptionFactory.wrapException("Error?building?SqlSession.",?e);??}?finally?{??//?關閉讀取器??ErrorContext.instance().reset();??try?{??reader.close();??}?catch?(IOException?e)?{??//?忽略關閉讀取器時可能拋出的異常??}??}??}??//?使用InputStream構建SqlSessionFactory??public?SqlSessionFactory?build(InputStream?inputStream)?{??return?build(inputStream,?null,?null);??}??//?使用InputStream構建SqlSessionFactory,并允許傳入Environment和Properties??public?SqlSessionFactory?build(InputStream?inputStream,?String?environment,?Properties?properties)?{??try?{??//?使用XML配置構建器創建Configuration對象??XMLConfigBuilder?parser?=?new?XMLConfigBuilder(inputStream,?environment,?properties);??//?解析配置文件,返回Configuration對象??Configuration?configuration?=?parser.parse();??//?基于Configuration對象創建SqlSessionFactory??return?new?SqlSessionFactoryBuilder.SqlSessionFactoryImpl(configuration);??}?catch?(Exception?e)?{??throw?ExceptionFactory.wrapException("Error?building?SqlSession.",?e);??}?finally?{??//?關閉輸入流??ErrorContext.instance().reset();??try?{??inputStream.close();??}?catch?(IOException?e)?{??//?忽略關閉輸入流時可能拋出的異常??}??}??}??//?SqlSessionFactoryImpl是SqlSessionFactory的默認實現??private?static?class?SqlSessionFactoryImpl?implements?SqlSessionFactory?{??private?final?Configuration?configuration;??private?SqlSessionFactoryImpl(Configuration?configuration)?{??this.configuration?=?configuration;??}??//?...?其他方法實現,例如openSession等??}??
}

解釋:?

1、SqlSessionFactoryBuilder?類提供了幾個重載的?build?方法,這些方法接收不同的參數(如?Reader或?InputStream),用于讀取?MyBatis?的配置文件。

2、在每個?build?方法中,首先創建了一個?XMLConfigBuilder?對象,這個對象負責解析 MyBatis 的 XML 配置文件。

3、XMLConfigBuilder?的?parse?方法被調用,它會讀取配置文件并構建?Configuration?對象,該對象包含了 MyBatis 的所有配置信息。

4、構建完?Configuration?對象后,使用它創建?SqlSessionFactory?的默認實現?SqlSessionFactoryImpl?的實例。

5、如果在解析配置文件或創建?SqlSessionFactory?的過程中發生異常,會捕獲異常并包裝為 MyBatis 自定義的異常類型。

6、在方法執行完畢后,無論是否發生異常,都會嘗試關閉?Reader?或?InputStream?以釋放資源。

7SqlSessionFactoryImpl?是?SqlSessionFactory?接口的一個默認實現,它內部持有?Configuration?對象,并提供了如?openSession?等方法用于創建?SqlSession

2、SqlSessionFactory:

  • SqlSessionFactory?是創建?SqlSession?的工廠類。

  • 它內部持有一個?Configuration?對象,該對象包含了 MyBatis 的所有配置信息。

  • 當調用?openSession?方法時,它會根據配置信息創建一個新的?SqlSession?實例。

SqlSessionFactory?在 MyBatis 中是一個核心接口,用于生產?SqlSession?對象。通常情況下,我們不會直接實現這個接口,而是使用?SqlSessionFactoryBuilder?來構建它的一個實現類實例。但是,為了解釋?SqlSessionFactory的作用,V哥先展示一個簡化的?SqlSessionFactory?接口和其可能的一個實現類的代碼。

首先是?SqlSessionFactory?接口的簡化版本:

import?org.apache.ibatis.session.SqlSession;??public?interface?SqlSessionFactory?{??/**??*?打開一個新的SqlSession。??*??*?@return?新的SqlSession實例??*?@throws?Exception?如果打開SqlSession時出錯??*/??SqlSession?openSession();??/**??*?打開一個新的SqlSession,并允許傳入執行器類型。??*??*?@param?executorType?執行器類型??*?@return?新的SqlSession實例??*?@throws?Exception?如果打開SqlSession時出錯??*/??SqlSession?openSession(ExecutorType?executorType);??/**??*?打開一個新的SqlSession,并允許傳入執行器類型和自動提交參數。??*??*?@param?executorType?執行器類型??*?@param?autoCommit?是否自動提交??*?@return?新的SqlSession實例??*?@throws?Exception?如果打開SqlSession時出錯??*/??SqlSession?openSession(ExecutorType?executorType,?boolean?autoCommit);??/**??*?打開一個新的SqlSession,并允許傳入配置屬性。??*??*?@param?properties?配置屬性??*?@return?新的SqlSession實例??*?@throws?Exception?如果打開SqlSession時出錯??*/??SqlSession?openSession(Properties?properties);??/**??*?打開一個新的SqlSession,并允許傳入執行器類型、自動提交參數和配置屬性。??*??*?@param?executorType?執行器類型??*?@param?autoCommit?是否自動提交??*?@param?properties?配置屬性??*?@return?新的SqlSession實例??*?@throws?Exception?如果打開SqlSession時出錯??*/??SqlSession?openSession(ExecutorType?executorType,?boolean?autoCommit,?Properties?properties);??//?...?可能還有其他方法,如關閉SqlSessionFactory等??
}

接下來是一個可能的?SqlSessionFactory?實現類的簡化版本(注意:MyBatis 并沒有直接提供一個名為?SqlSessionFactoryImpl?的類, V 哥這里只是為了演示):

import?org.apache.ibatis.executor.Executor;??
import?org.apache.ibatis.executor.ExecutorType;??
import?org.apache.ibatis.session.Configuration;??
import?org.apache.ibatis.session.SqlSession;??
import?org.apache.ibatis.session.SqlSessionFactory;??import?java.util.Properties;??public?class?SqlSessionFactoryImpl?implements?SqlSessionFactory?{??private?final?Configuration?configuration;??public?SqlSessionFactoryImpl(Configuration?configuration)?{??this.configuration?=?configuration;??}??@Override??public?SqlSession?openSession()?{??return?openSession(ExecutorType.SIMPLE);??}??@Override??public?SqlSession?openSession(ExecutorType?executorType)?{??return?openSession(executorType,?false);??}??@Override??public?SqlSession?openSession(ExecutorType?executorType,?boolean?autoCommit)?{??return?openSession(executorType,?autoCommit,?null);??}??@Override??public?SqlSession?openSession(Properties?properties)?{??return?openSession(ExecutorType.SIMPLE,?properties);??}??@Override??public?SqlSession?openSession(ExecutorType?executorType,?boolean?autoCommit,?Properties?properties)?{??//?創建Executor實例??Executor?executor?=?configuration.newExecutor(executorType,?autoCommit);??//?使用Configuration和Executor創建SqlSession??return?new?DefaultSqlSession(configuration,?executor);??}??//?...?其他方法實現,如關閉SqlSessionFactory等??
}

解釋:

1、SqlSessionFactory?接口定義了如何打開一個或多個?SqlSessionSqlSession?是 MyBatis 的核心接口,它提供了執行 SQL 語句和獲取映射結果的方法。

2、SqlSessionFactoryImpl?類是?SqlSessionFactory?接口的一個可能實現。在實際應用中,MyBatis 使用了不同的實現類,但原理類似。

3、SqlSessionFactoryImpl?的構造函數接收一個?Configuration?對象,該對象包含了 MyBatis 的所有配置信息,如環境設置、類型別名、映射文件等。

4、openSession?方法有多個重載版本,允許用戶指定執行器類型、是否自動提交事務以及配置屬性來打開?SqlSession。這些重載方法最終都會調用一個或多個帶有所有參數的?openSession?方法,以便在打開?SqlSession?時應用所有必要的配置。

5、在?openSession?方法中,根據傳入的執行器類型 (ExecutorType) 和是否自動提交 (autoCommit) 的參數,調用?Configuration?對象的?newExecutor?方法來創建一個新的執行器 (Executor) 實例。執行器負責管理和執行?SQL語句。

6、使用?Configuration?和?Executor?實例來創建一個新的?SqlSession?實例。這個?SqlSession?實例會用于執行?SQL?語句、獲取映射結果以及管理數據庫事務。

7、在實際應用中,SqlSessionFactory?通常通過?SqlSessionFactoryBuilder?構建。SqlSessionFactoryBuilder會讀取 MyBatis 的配置文件(通常是?XML?格式),解析配置信息,并創建一個?Configuration?對象。然后,使用這個?Configuration?對象來創建一個?SqlSessionFactory?實例。

8、SqlSessionFactory?是線程安全的,一旦創建,就可以在整個應用程序中重用。通常,每個應用程序只需要一個?SqlSessionFactory?實例。

9、SqlSession?則是非線程安全的,因此不應該在多個線程之間共享。每個線程應該有自己的?SqlSession?實例。使用完?SqlSession?后,應該調用其 close 方法來釋放資源。

10、SqlSessionFactory?和?SqlSession?的設計符合了工廠模式和單例模式的思想。SqlSessionFactory?負責生產?SqlSession,而?SqlSession?則負責執行具體的數據庫操作。

上面的代碼示例是一個簡化的版本,用于解釋 SqlSessionFactory 和其實現類的基本概念和工作原理。

3、SqlSession:

  • SqlSession?是執行?SQL?的核心接口。

  • 它通過?Executor?來執行?SQL?語句。

  • 當調用?selectOneselectListinsertupdatedelete?等方法時,實際上會調用?Executor?的相應方法。

  • SqlSession?也負責事務的管理,例如提交或回滾事務。

當涉及到?SqlSession?的源代碼時,實際上 MyBatis 框架的源代碼包含了多個與?SqlSession?相關的類,例如?DefaultSqlSession,這是?SqlSession?接口的一個常見實現。以下是一個簡化的?DefaultSqlSession?類的示例,V 哥會在代碼中加入中文注釋來解釋它的作用和功能:

import?org.apache.ibatis.executor.Executor;??
import?org.apache.ibatis.executor.statement.StatementHandler;??
import?org.apache.ibatis.mapping.MappedStatement;??
import?org.apache.ibatis.session.Configuration;??
import?org.apache.ibatis.session.ResultHandler;??
import?org.apache.ibatis.session.RowBounds;??
import?org.apache.ibatis.session.SqlSession;??import?java.util.List;??
import?java.util.Map;??public?class?DefaultSqlSession?implements?SqlSession?{??private?final?Configuration?configuration;??private?final?Executor?executor;??public?DefaultSqlSession(Configuration?configuration,?Executor?executor)?{??this.configuration?=?configuration;??this.executor?=?executor;??}??@Override??public?<T>?T?selectOne(String?statement,?Object?parameter)?{??//?根據statement和parameter獲取MappedStatement??MappedStatement?ms?=?configuration.getMappedStatement(statement);??//?創建StatementHandler??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?null,?null);??//?使用Executor執行查詢,并返回結果??return?executor.query(ms,?statementHandler);??}??@Override??public?<E>?List<E>?selectList(String?statement,?Object?parameter)?{??//?類似selectOne,但返回結果是List??MappedStatement?ms?=?configuration.getMappedStatement(statement);??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?null,?null);??return?executor.query(ms,?statementHandler,?RowBounds.DEFAULT,?ResultHandler.DEFAULT_RESULT_HANDLER);??}??@Override??public?<E>?List<E>?selectList(String?statement,?Object?parameter,?RowBounds?rowBounds)?{??//?與上一個selectList方法類似,但允許傳入RowBounds以進行分頁查詢??MappedStatement?ms?=?configuration.getMappedStatement(statement);??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?rowBounds,?null,?null);??return?executor.query(ms,?statementHandler,?rowBounds,?ResultHandler.DEFAULT_RESULT_HANDLER);??}??@Override??public?void?select(String?statement,?Object?parameter,?ResultHandler?resultHandler)?{??//?執行查詢,并將結果傳遞給ResultHandler進行處理??MappedStatement?ms?=?configuration.getMappedStatement(statement);??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?null,?null);??executor.query(ms,?statementHandler,?RowBounds.DEFAULT,?resultHandler);??}??@Override??public?int?insert(String?statement,?Object?parameter)?{??//?執行插入操作,并返回影響的記錄數??MappedStatement?ms?=?configuration.getMappedStatement(statement);??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?null,?null);??return?executor.update(ms,?statementHandler);??}??@Override??public?int?update(String?statement,?Object?parameter)?{??//?執行更新操作,并返回影響的記錄數??MappedStatement?ms?=?configuration.getMappedStatement(statement);??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?null,?null);??return?executor.update(ms,?statementHandler);??}??@Override??public?int?delete(String?statement,?Object?parameter)?{??//?執行刪除操作,并返回影響的記錄數??MappedStatement?ms?=?configuration.getMappedStatement(statement);??StatementHandler?statementHandler?=?configuration.newStatementHandler(this,?ms,?parameter,?RowBounds.DEFAULT,?null,?null);??return?executor.update(ms,?statementHandler);??}??@Override??public?<T>?T?getMapper(Class<T>?type)?{??//?獲取Mapper接口的代理實現??return?configuration.getMapper(type,?this);??}??//?...?可能還有其他方法,如提交事務、回滾事務、關閉SqlSession等??@Override??public?void?close()?{??//?清理資源,//?關閉SqlSession}//?...?省略其他可能的方法和細節

上面的代碼片段是?DefaultSqlSession?的簡化版本,用于解釋?SqlSession?的一些基本操作。下面 V 哥將對關鍵部分進行解釋:

  • 構造器:

    • DefaultSqlSession?的構造器接受一個?Configuration?對象和一個?Executor?對象。Configuration?對象包含了 MyBatis 的所有配置信息,而?Executor?對象則負責執行 SQL 語句。

  • 查詢方法:

    • selectOne,?selectList,?select?等方法用于執行查詢操作。它們首先從?Configuration?中獲取與提供的 SQL 語句標識符對應的?MappedStatement,然后創建一個?StatementHandler?來處理 SQL 語句的生成和參數綁定。最后,它們使用?Executor?來執行查詢并返回結果。

  • 增刪改方法:

    • insert,?update,?delete?等方法用于執行插入、更新和刪除操作。它們與查詢方法類似,但返回的是受影響的記錄數。

  • 獲取Mapper:

    • getMapper?方法用于獲取一個 Mapper 接口的代理實現。這允許你直接使用接口調用方法而無需手動創建和配置代理。

  • 關閉SqlSession:

    • close?方法用于關閉?SqlSession,釋放相關資源。

????????需要注意的是,SqlSession?是線程不安全的,因此通常每個線程都應該有自己的?SqlSession?實例。同時,SqlSession?的使用通常遵循“打開-執行-關閉”的模式,以確保資源的正確釋放。

????????在實際應用中,你通常不會直接創建?DefaultSqlSession?的實例,而是使用?SqlSessionFactory?來創建?SqlSessionSqlSessionFactory?負責根據配置創建?SqlSession?實例,并管理相關的資源。

????????希望這些注釋和解釋能夠幫助你理解?SqlSession?的作用和工作原理。如果需要更深入的理解,建議閱讀 MyBatis 的官方文檔和源代碼。

4、Mapper 接口及其實現:

  • Mapper?接口是開發者定義的,用于描述數據庫操作。

  • MyBatis?使用?JDK?動態代理為?Mapper?接口創建代理對象。

  • 當調用?Mapper?接口的方法時,代理對象會攔截調用,并轉換為?SQL?語句的執行。

  • 這個轉換過程涉及?MapperStatement?的查找和解析,以及參數和結果的映射。

????????在 MyBatis 中,Mapper?接口通常沒有直接的實現類,而是通過 MyBatis 的動態代理機制自動生成代理對象。Mapper接口定義了與數據庫操作相關的方法,而 MyBatis 會根據這些方法自動生成相應的 SQL 語句并執行。

????????下面是一個簡單的?Mapper?接口示例及注釋:

//?定義一個?Mapper?接口,用于映射數據庫操作??
public?interface?UserMapper?{??//?根據?ID?查詢用戶信息??//?@Select?注解用于指定查詢的?SQL?語句??//?#{id}?是參數占位符,表示方法參數??@Select("SELECT?*?FROM?user?WHERE?id?=?#{id}")??User?selectUserById(int?id);??//?插入用戶信息??//?@Insert?注解用于指定插入的?SQL?語句??//?使用?@Options?注解可以配置插入操作的一些選項,比如是否使用生成的鍵等??@Insert("INSERT?INTO?user?(name,?age)?VALUES?(#{name},?#{age})")??@Options(useGeneratedKeys?=?true,?keyProperty?=?"id")??int?insertUser(User?user);??//?更新用戶信息??//?@Update?注解用于指定更新的?SQL?語句??@Update("UPDATE?user?SET?name?=?#{name},?age?=?#{age}?WHERE?id?=?#{id}")??int?updateUser(User?user);??//?刪除用戶信息??//?@Delete?注解用于指定刪除的?SQL?語句??@Delete("DELETE?FROM?user?WHERE?id?=?#{id}")??int?deleteUser(int?id);??//?查詢所有用戶信息??//?@Select?注解指定查詢所有用戶的?SQL?語句??@Select("SELECT?*?FROM?user")??List<User>?selectAllUsers();??//?其他的數據庫操作方法...??
}

解釋:

????????接口定義:?UserMapper?是一個接口,它定義了與?user?表相關的數據庫操作。 注解: MyBatis 提供了注解(如?@Select@Insert@Update@Delete)來簡化?SQL?語句的編寫。這些注解允許你在接口方法上直接指定 SQL 語句。

????????參數占位符:?在?SQL?語句中,#{id}、#{name}、#{age}?等是參數占位符,它們會在運行時被方法參數的實際值替換。

????????自動映射:?MyBatis 會自動將查詢結果映射到?User?類型的對象上,前提是你的?User?類的屬性名稱和數據庫表的列名能夠對應上。

????????動態代理:?當你在 MyBatis 的?SqlSession?中調用?getMapper(UserMapper.class)?時,MyBatis 會根據?UserMapper?接口動態生成一個代理對象。這個代理對象會在運行時攔截方法調用,并自動執行相應的?SQL?語句。

????????選項配置:?@Options?注解用于配置?SQL?語句執行的一些選項。例如,在插入操作中,useGeneratedKeys = true?表示使用數據庫自動生成的主鍵,keyProperty = "id"?指定將生成的主鍵設置到?User?對象的?id?屬性上。

????????返回類型:?方法的返回類型通常與?SQL?語句的執行結果相對應。例如,查詢單個用戶返回?User?對象,查詢多個用戶返回?List<User>

????????在實際應用中,你通常不需要手動編寫?Mapper?接口的實現類。你只需要定義接口,并在?XML?映射文件(如果不使用注解)或注解中編寫?SQL?語句。MyBatis 會負責接口的動態代理實現和?SQL?語句的執行。這大大簡化了數據庫操作的開發過程。

5、MappedStatement:

  • MappedStatement 是 MyBatis 內部表示一個 SQL 映射語句的對象。

  • 它包含 SQL 語句、參數類型、結果映射等信息。

  • 當 MyBatis 解析 Mapper XML 文件時,會為每個 SQL 語句創建一個 MappedStatement 對象,并存儲在 Configuration 對象中。

  • 執行 SQL 時,MyBatis 會根據方法簽名或 ID 查找對應的 MappedStatement。

????????MappedStatement?是 MyBatis 中的一個核心類,它代表了一個映射語句,即一個?SQL?語句及其相關的配置信息。在 MyBatis 中,MappedStatement?對象是由 MyBatis 在解析?XML?映射文件或注解時創建的,并存儲在?Configuration對象中。

????????由于?MappedStatement?是 MyBatis 內部使用的核心類,其實現細節和源代碼通常較為復雜,不適合在這里完整地列出。不過,我可以為你提供一個簡化版的?MappedStatement?類結構,并添加必要的注釋來解釋其主要組成部分。

????????請注意,以下代碼僅用于解釋目的,幫助你更好的理解:

//?MappedStatement?類簡化版,用于解釋其主要組成部分??
public?class?MappedStatement?{??//?映射語句的唯一標識符??private?String?id;??//?映射語句對應的?SQL?語句??private?String?sql;??//?映射語句的類型(SELECT,?INSERT,?UPDATE,?DELETE)??private?SqlCommandType?sqlCommandType;??//?參數類型,即傳遞給?SQL?語句的參數的類型??private?Class<?>?parameterType;??//?結果類型,即?SQL?語句執行后返回的結果的類型??private?Class<?>?resultType;??//?語句的結果映射配置??private?ResultMap?resultMap;??//?語句使用的數據庫?ID(用于分庫分表等情況)??private?String?databaseId;??//?語句使用的參數處理器類型??private?Class<??extends?ParameterHandler>?parameterHandlerType;??//?語句使用的結果處理器類型??private?Class<??extends?ResultHandler>?resultHandlerType;??//?語句使用的?SQL?語句解析器類型??private?Class<??extends?StatementHandler>?statementHandlerType;??//?語句使用的綁定器類型??private?Class<??extends?TypeHandler>?boundSqlTypeHandler;??//?語句的插件列表??private?List<Interceptor>?interceptors;??//?...?可能還有其他字段和方法??//?構造函數(通常不是直接創建的,而是通過?MyBatis?的內部機制)??public?MappedStatement(String?id,?String?sql,?SqlCommandType?sqlCommandType,?Class<?>?parameterType,??Class<?>?resultType,?ResultMap?resultMap,?String?databaseId,??Class<??extends?ParameterHandler>?parameterHandlerType,??Class<??extends?ResultHandler>?resultHandlerType,??Class<??extends?StatementHandler>?statementHandlerType,??Class<??extends?TypeHandler>?boundSqlTypeHandler,??List<Interceptor>?interceptors)?{??this.id?=?id;??this.sql?=?sql;??this.sqlCommandType?=?sqlCommandType;??this.parameterType?=?parameterType;??this.resultType?=?resultType;??this.resultMap?=?resultMap;??this.databaseId?=?databaseId;??this.parameterHandlerType?=?parameterHandlerType;??this.resultHandlerType?=?resultHandlerType;??this.statementHandlerType?=?statementHandlerType;??this.boundSqlTypeHandler?=?boundSqlTypeHandler;??this.interceptors?=?interceptors;??}??//?Getter?和?Setter?方法省略...??//?...?可能還有其他方法,如執行?SQL?語句、獲取綁定參數等??
}

解釋:

????????1、標識符?id: 每個?MappedStatement?對象都有一個唯一的標識符,它通常對應于?Mapper?接口中的一個方法名。

????????2、SQL?語句?sql: 存儲了具體的?SQL?語句字符串。

? ? ? ? 3、語句類型?sqlCommandType: 表示這個映射語句是查詢、插入、更新還是刪除操作。

????????4、參數類型?parameterType?和結果類型?resultType: 分別表示傳遞給?SQL?語句的參數類型和?SQL?語句執行后返回的結果類型。

????????5、結果映射?resultMap: 用于復雜結果集的映射配置。

????????6、數據庫?ID?databaseId: 用于分庫分表等高級功能。

????????7、處理器類型: 包括參數處理器?parameterHandlerType、結果處理器?resultHandlerType、語句處理器?statementHandlerType?和綁定器?boundSqlTypeHandler,它們都是用于處理?SQL?語句執行過程中不同階段的任務的類型。

????????8、插件列表?interceptors: 存儲了應用于這個映射語句的插件列表,插件可以用于攔截和修改?SQL?語句的執行過程。

????????在實際應用中,MappedStatement?對象是由 MyBatis 在啟動時解析?XML?映射文件或注解時創建的,并存儲在?Configuration?對象中。當執行數據庫操作時,MyBatis 會根據?Mapper?接口方法的名稱查找對應的?MappedStatement?對象,并使用其中的信息來構建和執行?SQL?語句。

????????由于?MappedStatement?是 MyBatis內部實現的一部分,它的具體細節可能會隨著?MyBatis?的版本更新而有所變化。然而,其核心功能和設計原則通常保持一致:為?SQL?映射語句提供元數據信息和運行時環境。

????????在實際的 MyBatis 實現中,MappedStatement?類通常包含更多的字段和方法,用于處理更復雜的場景,比如動態?SQL、緩存配置、結果集映射、條件分支等等。它通常還與 MyBatis 的其他關鍵組件如?SqlSessionExecutorStatementHandler?等緊密協作,以完成?SQL?語句的執行和結果處理。

????????當你使用 MyBatis 時,你通常不需要直接創建或操作?MappedStatement?對象。相反,你會通過定義?Mapper?接口和?XML?映射文件來聲明你的?SQL?映射語句,然后 MyBatis 會自動為你處理?MappedStatement?的創建和管理。

6、Executor:

  • Executor 是 SQL 語句執行的核心。

  • 它有三個實現類:SimpleExecutor、ReuseExecutor 和 BatchExecutor,分別對應不同的執行策略。

  • Executor 負責與 JDBC 交互,包括創建 PreparedStatement、設置參數、執行 SQL、處理結果等。

  • 它會使用 TypeHandler 來處理參數和結果集的轉換。

????????由于?Executor?類是 MyBatis 框架中的核心組件,其源代碼相對較長且涉及多個內部類和復雜邏輯。在這里,V 哥將為你提供一個簡化版的?Executor?類及其部分實現,來解釋其主要功能。

//?Executor接口,定義了執行SQL語句的方法??
public?interface?Executor?{??//?執行更新操作(插入、更新、刪除)??int?update(MappedStatement?ms,?Object?parameter);??//?執行查詢操作,返回結果列表??<E>?List<E>?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?cacheKey,?BoundSql?boundSql);??//?執行查詢操作,返回單個結果??<E>?E?query(MappedStatement?ms,?Object?parameter,?ResultHandler?resultHandler,?CacheKey?cacheKey,?BoundSql?boundSql);??//?執行查詢操作,返回結果集游標??<E>?Cursor<E>?queryCursor(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?cacheKey,?BoundSql?boundSql);??//?刷新緩存??void?flushStatements();??//?關閉Executor,釋放資源??void?close(boolean?forceClose);??//?是否已關閉??boolean?isClosed();??//?獲取事務對象??Transaction?getTransaction();??//?延遲加載是否開啟??boolean?isLazyLoadEnabled();??//?設置延遲加載是否開啟??void?setLazyLoadEnabled(boolean?lazyLoadEnabled);??
}??//?BaseExecutor類,Executor接口的一個基礎實現類??
public?abstract?class?BaseExecutor?implements?Executor?{??protected?final?Configuration?configuration;??protected?final?Transaction?transaction;??protected?ErrorContext?errorContext;??public?BaseExecutor(Configuration?configuration,?Transaction?transaction)?{??this.configuration?=?configuration;??this.transaction?=?transaction;??this.errorContext?=?new?ErrorContext();??}??//?省略其他方法...??//?更新操作實現??@Override??public?int?update(MappedStatement?ms,?Object?parameter)?{??//?...?更新操作的實現邏輯,包括預處理語句、設置參數、執行更新等??}??//?查詢操作實現(返回結果列表)??@Override??public?<E>?List<E>?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?cacheKey,?BoundSql?boundSql)?{??//?...?查詢操作的實現邏輯,包括預處理語句、設置參數、執行查詢、處理結果集等??}??//?...?其他方法的實現...??
}??//?SimpleExecutor類,BaseExecutor的一個簡單實現,用于執行SQL語句??
public?class?SimpleExecutor?extends?BaseExecutor?{??public?SimpleExecutor(Configuration?configuration,?Transaction?transaction)?{??super(configuration,?transaction);??}??//?更新操作實現(繼承自BaseExecutor)??@Override??public?int?update(MappedStatement?ms,?Object?parameter)?{??//?這里可以添加SimpleExecutor特有的邏輯,或者直接調用父類的實現??return?super.update(ms,?parameter);??}??//?查詢操作實現(繼承自BaseExecutor)??@Override??public?<E>?List<E>?query(MappedStatement?ms,?Object?parameter,?RowBounds?rowBounds,?ResultHandler?resultHandler,?CacheKey?cacheKey,?BoundSql?boundSql)?{??//?這里可以添加SimpleExecutor特有的邏輯,或者直接調用父類的實現??return?super.query(ms,?parameter,?rowBounds,?resultHandler,?cacheKey,?boundSql);??}??//?...?其他方法的實現...??
}

解釋:

????????1、Executor?接口: 定義了執行?SQL?語句所需的方法,包括更新、查詢等。它是 MyBatis 中執行器模式的核心部分,允許不同的執行策略(如批處理、重用預處理語句等)通過不同的實現類來實現。

????????2、BaseExecutor?類: 是?Executor?接口的一個基礎實現類,提供了執行器的一些通用邏輯。它通常包含配置信息、事務對象和錯誤上下文等成員變量。BaseExecutor?提供了對?SQL?語句執行的基礎支持,但具體的執行邏輯可能由其子類實現。

????????3、SimpleExecutor?類: 是?BaseExecutor?的一個具體實現,它可能不包含復雜的邏輯或優化,但提供了基本的?SQL執行功能。在實際應用中,MyBatis 可能提供了更多的執行器實現類,比如?ReuseExecutor?用于重用預處理語句,BatchExecutor?用于批量執行等。

????????在 MyBatis 的實際實現中,Executor?類及其實現通常包含更多的成員變量、方法和復雜的邏輯,以處理SQL語句的解析、參數綁定、結果映射以及緩存等高級功能。此外,Executor?類通常還會與其他組件如?StatementHandlerParameterHandlerResultSetHandler?和?TypeHandler?等緊密合作,以構建和執行完整的SQL執行流程。

下面,V 哥將進一步解釋?Executor?及其實現類在 MyBatis 中的一些核心功能:

1、SQL解析與綁定:

  • Executor?接收?MappedStatement?作為輸入,該對象包含了SQL語句的元數據信息。

  • 使用?ParameterHandler?處理參數綁定,將用戶提供的參數轉換為JDBC可以理解的格式,并設置到預處理語句中。

2、執行SQL語句:

  • 調用JDBC的?Statement?或?PreparedStatement?執行SQL語句。

  • Executor?可能管理自己的預處理語句緩存,以提高性能。

3、結果處理:

  • 使用?ResultSetHandler?將JDBC的?ResultSet?轉換為Java對象列表。

  • 涉及類型轉換和結果映射,使用?TypeHandler?來處理字段類型和Java類型之間的轉換。

4、事務管理:

  • Executor?通常與事務管理對象(如?Transaction)一起工作,以確保SQL操作在事務的上下文中執行。

  • 負責提交或回滾事務,以處理成功或失敗的SQL操作。

5、緩存管理:

  • MyBatis 提供了一級緩存和二級緩存機制,Executor?負責管理這些緩存。

  • 在執行查詢時,首先檢查緩存中是否有結果,如果有則直接返回,避免重復執行SQL

6、延遲加載:

  • MyBatis 支持延遲加載,即當需要時才加載關聯數據。

  • Executor?需要在適當的時候觸發延遲加載的執行。

MyBatis 提供了多種?Executor?實現類,它們之間的主要差異在于執行策略和資源管理:

SIMPLE: 最基本的實現,每次執行都創建一個新的預處理語句。

REUSE: 重用預處理語句,以減少JDBC對象的創建和銷毀開銷。

BATCH: 批量執行SQL語句,適用于大量數據的插入、更新或刪除操作。

每種實現都有其特定的使用場景和性能特點,用戶可以根據應用的需求選擇合適的實現。

Executor?是 MyBatis 框架中的核心組件之一,它負責執行SQL語句并處理結果。通過不同的實現類,MyBatis 提供了靈活的執行策略,以滿足不同應用場景的性能需求。在實際應用中,用戶通常不需要直接創建或管理?Executor?對象,而是通過配置和使用 MyBatis 的?API?來間接使用它。

7、TypeHandler:

  • TypeHandler?是 Java 類型和?JDBC?類型之間的橋梁。

  • MyBatis 提供了一系列內置的?TypeHandler,如?StringTypeHandlerIntegerTypeHandler?等。

  • 當需要自定義類型轉換時,開發者可以實現自己的?TypeHandler

  • TypeHandler?負責將 Java 對象轉換為?JDBC?參數,以及將 JDBC 結果集轉換為 Java 對象。

TypeHandler?是 MyBatis 中一個非常核心的組件,它負責 Java 類型和?JDBC?類型之間的轉換。TypeHandler?定義了類型轉換的接口,并提供了一些基礎實現。以下是一個簡化版的?TypeHandler?接口及其一個實現類的示例。

//?TypeHandler接口,定義了類型轉換的方法??
public?interface?TypeHandler<T>?{??//?設置參數值??void?setParameter(PreparedStatement?ps,?int?i,?T?parameter,?JdbcType?jdbcType)?throws?SQLException;??//?從結果集中獲取值??T?getResult(ResultSet?rs,?String?columnName)?throws?SQLException;??//?從結果集中獲取值(使用列索引)??T?getResult(ResultSet?rs,?int?columnIndex)?throws?SQLException;??//?從CallableStatement中獲取值??T?getResult(CallableStatement?cs,?int?columnIndex)?throws?SQLException;??
}??//?BaseTypeHandler類,TypeHandler的一個基礎實現類,提供了默認的類型轉換邏輯??
public?abstract?class?BaseTypeHandler<T>?extends?TypeReference<T>?implements?TypeHandler<T>?{??//?設置參數值(默認實現,子類可覆蓋)??@Override??public?void?setParameter(PreparedStatement?ps,?int?i,?T?parameter,?JdbcType?jdbcType)?throws?SQLException?{??if?(parameter?==?null)?{??if?(jdbcType?==?null)?{??throw?new?TypeException("JDBC?requires?that?the?JdbcType?must?be?specified?for?all?nullable?parameters.");??}??ps.setNull(i,?jdbcType.TYPE_CODE);??}?else?{??setNonNullParameter(ps,?i,?parameter,?jdbcType);??}??}??//?設置非空參數值(子類需要實現這個方法)??protected?abstract?void?setNonNullParameter(PreparedStatement?ps,?int?i,?T?parameter,?JdbcType?jdbcType)?throws?SQLException;??//?從結果集中獲取值(默認實現,子類可覆蓋)??@Override??public?T?getResult(ResultSet?rs,?String?columnName)?throws?SQLException?{??return?getResult(rs,?rs.findColumn(columnName));??}??//?從結果集中獲取值(默認實現,子類需要實現這個方法)??@Override??public?T?getResult(ResultSet?rs,?int?columnIndex)?throws?SQLException?{??return?getNullableResult(rs,?columnIndex);??}??//?從CallableStatement中獲取值(默認實現,子類需要實現這個方法)??@Override??public?T?getResult(CallableStatement?cs,?int?columnIndex)?throws?SQLException?{??return?getNullableResult(cs,?columnIndex);??}??//?獲取非空結果(子類需要實現這個方法)??protected?abstract?T?getNullableResult(ResultSet?rs,?String?columnName)?throws?SQLException;??//?獲取非空結果(子類需要實現這個方法)??protected?abstract?T?getNullableResult(ResultSet?rs,?int?columnIndex)?throws?SQLException;??//?獲取非空結果(子類需要實現這個方法)??protected?abstract?T?getNullableResult(CallableStatement?cs,?int?columnIndex)?throws?SQLException;??
}??//?IntegerTypeHandler類,TypeHandler的一個具體實現,用于處理Integer類型的轉換??
public?class?IntegerTypeHandler?extends?BaseTypeHandler<Integer>?{??//?設置非空參數值??@Override??protected?void?setNonNullParameter(PreparedStatement?ps,?int?i,?Integer?parameter,?JdbcType?jdbcType)?throws?SQLException?{??ps.setInt(i,?parameter);??}??//?從結果集中獲取非空Integer值??@Override??protected?Integer?getNullableResult(ResultSet?rs,?String?columnName)?throws?SQLException?{??return?rs.getInt(columnName);??}??//?從結果集中獲取非空Integer值(使用列索引)??@Override??protected?Integer?getNullableResult(ResultSet?rs,?int?columnIndex)?throws?SQLException?{??return?rs.getInt(columnIndex);??}??//?從CallableStatement中獲取非空Integer值??@Override??protected?Integer?getNullableResult(CallableStatement?cs,?int?columnIndex)?throws?SQLException?{??return?cs.getInt(columnIndex);??}??
}

解釋:?

1、TypeHandler 接口:

  • setParameter?方法:將 Java 類型的參數設置到?PreparedStatement?對象中,以便執行?SQL?語句。

  • getResult?方法:從?ResultSet?或?CallableStatement?對象中獲取指定列的結果,并將其轉換為 Java 類型。

2、BaseTypeHandler 抽象類:

  • setParameter?方法提供了默認實現,用于處理?null?值和?JDBC?類型的設置。

  • setNonNullParameter?是一個抽象方法,子類需要實現,用于處理非空值的設置。

  • getResult方法也提供了默認實現,它們通常調用?getNullableResult?抽象方法,子類需要實現具體的轉換邏輯。

3、IntegerTypeHandler 類:

  • 繼承自?BaseTypeHandler<Integer>,專門用于處理?Integer?類型的轉換。

  • 實現了?setNonNullParameter?方法,用于將?Integer?類型的參數設置到?PreparedStatement?中。

  • 實現了?getNullableResult?方法的三個重載版本,用于從?ResultSet?或?CallableStatement?中獲取?Integer?類型的結果。

使用場景:

????????當 MyBatis 執行?SQL?語句時,它需要根據 Java 類型的參數和?SQL?查詢的結果來設置參數和獲取結果。這時,MyBatis 會查找合適的?TypeHandler?來執行這些類型轉換。如果 MyBatis 提供了現成的?TypeHandler(如?IntegerTypeHandler),它可以直接使用。如果沒有現成的?TypeHandler,用戶也可以自定義?TypeHandler?來處理特殊的類型轉換邏輯。

????????TypeHandler?接口及其實現類在 MyBatis 中扮演了非常重要的角色,它們負責在 Java 類型和 JDBC 類型之間進行轉換,使得 MyBatis 能夠靈活地處理各種類型的參數和結果集。通過自定義?TypeHandler,用戶可以擴展 MyBatis 的類型轉換能力,以滿足不同的業務需求。

8、Plugin:

  • Plugin?是 MyBatis 的插件機制,允許開發者在核心流程中插入自定義邏輯。

  • 插件通過實現?Interceptor?接口并覆蓋?intercept?方法來定義自己的攔截邏輯。

  • 插件在 MyBatis 初始化時通過?Plugin?類進行包裝,并插入到目標對象的代理鏈中。

  • 當目標對象的方法被調用時,插件的攔截邏輯會先被執行。

??Plugin?類在 MyBatis 中通常用于攔截和修改 MyBatis 的核心行為。它允許用戶在不修改 MyBatis 核心代碼的情況下,對?SQL?語句的生成、參數設置、結果集處理等過程進行自定義處理。以下是一個簡化版的?Plugin?類及其實現。

//?Plugin接口,定義插件需要實現的方法??
public?interface?Plugin?{??//?包裹目標對象,返回一個被攔截對象??Object?wrap(Object?target);??//?獲取插件的屬性??Class<?>?getType();??//?獲取插件的處理程序??Interceptor?getInterceptor();??//?插件是否可以被用于目標對象??boolean?isTarget(Object?target);??//?靜態方法,用于生成插件實例??static?Object?wrap(Object?target,?Interceptor?interceptor,?Class<?>?type)?{??//?創建Plugin對象??Plugin?plugin?=?new?Plugin(target,?interceptor,?type);??//?返回被攔截的目標對象??return?plugin.wrap(target);??}??//?Plugin類的私有構造器,防止外部直接實例化??private?Plugin(Object?target,?Interceptor?interceptor,?Class<?>?type)?{??//?初始化成員變量??this.target?=?target;??this.interceptor?=?interceptor;??this.type?=?type;??}??//?成員變量??private?Object?target;??private?Interceptor?interceptor;??private?Class<?>?type;??
}??//?Interceptor接口,定義插件需要實現的攔截方法??
public?interface?Interceptor?{??//?插件在MyBatis初始化時調用??void?intercept(Invocation?invocation)?throws?Throwable;??//?插件的ID,用于唯一標識插件??Object?plugin(Object?target);??//?插件的屬性集合??void?setProperties(Properties?properties);??
}??//?假設我們有一個實現Interceptor接口的自定義插件??
public?class?MyCustomPlugin?implements?Interceptor?{??//?插件的屬性??private?String?someProperty;??@Override??public?Object?intercept(Invocation?invocation)?throws?Throwable?{??//?在這里編寫攔截邏輯??//?例如,可以修改SQL語句、參數等??System.out.println("Intercepted?method:?"?+?invocation.getMethod().getName());??//?繼續執行原始邏輯??return?invocation.proceed();??}??@Override??public?Object?plugin(Object?target)?{??//?在這里可以對目標對象進行包裝或處理??return?Plugin.wrap(target,?this,?MyCustomPlugin.class);??}??@Override??public?void?setProperties(Properties?properties)?{??//?設置插件的屬性??this.someProperty?=?properties.getProperty("someProperty");??}??
}

解釋:

1、Plugin 接口:

  • wrap(Object target): 這是一個用于包裝目標對象的方法,通常會在插件初始化時被調用,返回被包裝后的對象,這個對象會代理目標對象的行為,并在必要時插入攔截邏輯。

  • getType(): 返回插件的類類型。

  • getInterceptor(): 返回插件的攔截器實現。

  • isTarget(Object target): 判斷插件是否適用于目標對象。

  • wrap(Object target, Interceptor interceptor, Class<?> type): 這是一個靜態方法,用于創建并返回 Plugin 實例,同時完成目標對象的包裝。

2、Interceptor 接口:

  • intercept(Invocation invocation): 這是插件的核心方法,當目標對象的方法被調用時,這個方法會被執行。在這里,你可以編寫自定義的攔截邏輯。

  • plugin(Object target): 這是一個用于包裝目標對象的方法,返回包裝后的對象。在 MyBatis 中,這個方法通常與?Plugin?接口的?wrap?方法結合使用,以創建代理對象。

  • setProperties(Properties properties): 這是一個設置插件屬性的方法,MyBatis 在配置插件時會調用此方法。

3、MyCustomPlugin 類:

  • 這個類實現了?Interceptor?接口,是自定義插件的具體實現。

  • 在?intercept?方法中,你可以編寫攔截目標對象方法執行的代碼,例如修改?SQL?語句、修改參數等。

  • plugin?方法返回包裝后的目標對象,通常直接調用?Plugin.wrap?方法。

  • setProperties?方法用于設置插件的配置屬性。

使用場景:

????????當你在 MyBatis 中需要修改 SQL 語句、參數設置或結果集處理時,你可以編寫一個自定義的?Interceptor?實現,并使用?Plugin?接口來包裝目標對象,從而在不修改 MyBatis 核心代碼的情況下擴展其功能。在 MyBatis 的配置文件中配置插件后,MyBatis 會在啟動時加載插件。

Plugin 類的使用:

在 MyBatis 中,Plugin 類的使用通常涉及到以下步驟:

1、編寫自定義插件:

  • 創建一個類實現?Interceptor?接口,實現其中的?interceptplugin?和?setProperties?方法。

  • 在?intercept?方法中編寫攔截邏輯,比如修改?SQL?語句、參數或處理結果集。

  • 在?plugin?方法中調用?Plugin.wrap?方法包裝目標對象。

  • 在?setProperties?方法中處理插件配置屬性。

2、配置插件:

  • 在 MyBatis 的配置文件(通常是?mybatis-config.xml)中,使用?<plugins>?元素配置插件。

  • 在?<plugin>?子元素中指定插件的?interceptor?實現類,以及可能的屬性。

3、啟動 MyBatis:

  • 當 MyBatis 啟動時,它會加載并初始化配置的插件。

  • 插件的?intercept?方法會在相應的方法調用時被觸發。

示例配置:

在?mybatis-config.xml?配置文件中配置自定義插件:

<configuration>??<!--?其他配置?-->??<plugins>??<plugin?interceptor="com.example.MyCustomPlugin">??<property?name="someProperty"?value="someValue"/>??</plugin>??</plugins>??<!--?其他配置?-->??
</configuration>

Plugin 類的實現細節:

????????在?Plugin?類的實現中,通常會使用動態代理技術來包裝目標對象。當目標對象的方法被調用時,動態代理會攔截調用,并首先執行插件的攔截邏輯,然后再調用原始方法。

????????Plugin 類中的?wrap?方法通常利用 Java 的反射?API?和動態代理(例如?JDK?動態代理或?CGLIB)來創建目標對象的代理。代理對象會實現目標對象的接口,并在調用方法時執行攔截邏輯。

注意:

  • 插件的?intercept?方法必須謹慎處理,避免引入性能問題或破壞 MyBatis 的行為。

  • 插件的?plugin?方法必須正確處理目標對象,確保返回的是正確的代理對象。

  • 插件的?setProperties?方法應該能夠處理所有必要的配置屬性,并在需要時驗證它們的值。

??Plugin?類在 MyBatis 中是一個非常重要的機制,它允許用戶在不修改 MyBatis 核心代碼的情況下擴展其功能。通過編寫自定義的?Interceptor?實現,并正確配置插件,用戶可以攔截和修改 MyBatis 的行為,以滿足特定的業務需求。在實際應用中,需要深入理解 MyBatis 的內部機制和動態代理技術,才能有效地使用?Plugin?類來擴展 MyBatis 的功能。

MyBatis整體架構圖

????????MyBatis 分為三層架構,分別是基礎支撐層、核心處理層和接口層,如上兩圖所示。

1. 基礎支撐層


1.1 類型轉換模塊

? ? ? ? <typeAliase> 標簽的別名機制,由基礎支撐層中的類型轉換模塊實現的;
????????JDBC 類型與 Java 類型之間的相互轉換,綁定實參、映射 ResultSet 場景中都有所體現:??????? ? ??

  • 在 SQL 模板綁定用戶傳入實參的場景中,類型轉換模塊會將 Java 類型數據轉換成 JDBC 類型數據;
  • 在將 ResultSet 映射成結果對象的時候,類型轉換模塊會將 JDBC 類型數據轉換成 Java 類型數據。


1.2 日志模塊

? ? ? ? MyBatis 提供了日志模塊來集成 Java 生態中的第三方日志框架,該模塊目前可以集成 Log4j、Log4j2、slf4j 等優秀的日志框架。

1.3 反射工具模塊

????????MyBatis 的反射工具箱是在 Java 反射的基礎之上進行的一層封裝,為上層使用方提供更加靈活、方便的 API 接口,同時緩存 Java 的原生反射相關的元數據,提升了反射代碼執行的效率,優化了反射操作的性能。

1.4 Binding 模塊

????????通過 SqlSession 獲取 Mapper 接口的代理,然后通過這個代理執行關聯 Mapper.xml 文件中的數據庫操作。通過這種方式,可以將一些錯誤提前到編譯期,該功能就是通過 Binding 模塊完成的。

1.5 數據源模塊

????????持久層框架核心組件之一就是數據源,MyBatis 自身提供了一套不錯的數據源實現,也是 MyBatis 的默認實現。MyBatis 的數據源模塊中也提供了與第三方數據源集成的相關接口,這也為用戶提供了更多的選擇空間,提升了數據源切換的靈活性。

1.6緩存模塊

????????數據庫是實踐生成中非常核心的存儲,數據庫性能的優劣直接影響了上層業務系統的優劣。
很多線上業務都是讀多寫少的場景,在數據庫遇到瓶頸時,緩存是最有效、最常用的手段之一(如下圖所示),正確使用緩存可以將一部分數據庫請求攔截在緩存這一層,這就能夠減少一部分數據庫的壓力,提高系統性能。


????????MyBatis 就提供了一級緩存和二級緩存,具體實現位于基礎支撐層的緩存模塊中。

1.7 解析器模塊

????????mybatis-config.xml 配置文件和 Mapper.xml 配置文件的解析。

1.8 事務管理模塊

????????持久層框架一般都會提供一套事務管理機制實現數據庫的事務控制,MyBatis 對數據庫中的事務進行了一層簡單的抽象,提供了簡單易用的事務接口和實現。一般情況下,Java 項目都會集成 Spring,并由 Spring 框架管理事務。

2. 核心處理層

????????核心處理層是 MyBatis 核心實現所在,其中涉及 MyBatis 的初始化以及執行一條 SQL 語句的全流程。

2.1 配置解析

????????MyBatis 有三處可以添加配置信息的地方,分別是:mybatis-config.xml 配置文件、Mapper.xml 配置文件以及 Mapper 接口中的注解信息。在 MyBatis 初始化過程中,會加載這些配置信息,并將解析之后得到的配置對象保存到 Configuration 對象中。

2.2 SQL 解析與 scripting 模塊

????????MyBatis 的最大亮點應該要數其動態 SQL 功能了,只需要通過 MyBatis 提供的標簽即可根據實際的運行條件動態生成實際執行的 SQL 語句。MyBatis 提供的動態 SQL 標簽非常豐富,包括 <where> 標簽、<if> 標簽、<foreach> 標簽、<set> 標簽等。

????????MyBatis 中的 scripting 模塊就是負責動態生成 SQL 的核心模塊。它會根據運行時用戶傳入的實參,解析動態 SQL 中的標簽,并形成 SQL 模板,然后處理 SQL 模板中的占位符,用運行時的實參填充占位符,得到數據庫真正可執行的 SQL 語句。

2.3 SQL 執行

????????要執行一條 SQL 語句,會涉及非常多的組件,比較核心的有:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler。

????????其中,Executor 會調用事務管理模塊實現事務的相關控制,同時會通過緩存模塊管理一級緩存和二級緩存。SQL 語句的真正執行將會由 StatementHandler 實現。StatementHandler 會先依賴 ParameterHandler 進行 SQL 模板的實參綁定,然后由 java.sql.Statement 對象將 SQL 語句以及綁定好的實參傳到數據庫執行,從數據庫中拿到 ResultSet,最后,由 ResultSetHandler 將 ResultSet 映射成 Java 對象返回給調用方,這就是 SQL 執行模塊的核心。

2.4 插件

????????很多成熟的開源框架,都會以各種方式提供擴展能力。當框架原生能力不能滿足某些場景的時候,就可以針對這些場景實現一些插件來滿足需求,這樣的框架才能有足夠的生命力。這也是 MyBatis 插件接口存在的意義。

3. 接口層

????????接口層是 MyBatis 暴露給調用的接口集合,這些接口都是使用 MyBatis 時最常用的一些接口,例如,SqlSession 接口、SqlSessionFactory 接口等。其中,最核心的是 SqlSession 接口,你可以通過它實現很多功能,例如,獲取 Mapper 代理、執行 SQL 語句、控制事務開關等。

架構流程圖

執行流程

? ? ? ?(1) MyBatis配置文件config.xml:配置了全局配置文件,配置了MyBatis的運行環境等信息。mapper,xml:sql的映射文件,配置了操作數據庫的sql語句,此文件需在config.xml中加載。????????

? ? ? ? (2)SqlSessionFactory:通過MyBatis環境等配置信息構造SqlSessionFactory(會話工廠)。

? ? ? ? (3)SqlSession:通過會話工廠創建SqlSession(會話),對數據庫進行增刪改查操作。

? ? ? ? (4)Exector執行器:MyBatis底層自定義了Exector執行器接口來具體操作數據庫,Exector接口有兩個實現,一個基本執行器(默認),一個是緩存執行器,SqlSession底層是通過Exector接口操作數據庫。

? ? ? ? (5)MappedStatement:MyBatis的一個底層封裝對象,它包裝了MyBatis配置信息與sql映射信息等。mapper.xml中的insert/select/update/delete標簽對應一個MappedStatement對象。標簽的id就是MappedStatement的id。

????????MappedStatement對sql執行輸入參數進行定義,包括HashMap、基本類型、pojo、Executor通過MappedStatement在執行sql前將輸入的Java對象映射至sql中,輸入參數映射就是JDBC編程對preparedStatement設置參數。MappedStatement對sql執行輸出結果進行定義,包括HashMap、基本類型、pojo,Executor通過MappedStatement在執行sql后將輸出結果映射至Java對象中,輸出結果映射就是JDBC編程對結果的解析處理過程。

調用流程圖

Mapper代理執行原理

Mapper代理開發方式使用的是JDK的動態代理(針對有接口的類進行動態代理)。

Springboot整合Mybatis的流程

1.查詢前

也就是springboot啟動時做的工作

實例化SqlSessionFactory
????????1.構建一個DefaultSqlSessionFactory,主要作用就是維護Configuration和查詢時獲取DefaultSqlSession,然后通過DefaultSqlSession執行查詢操作。
????????2.實例化的過程中會解析mapper.xml中的各種標簽封裝成xxxsqlSource,保存在Configuration的mappedstatelment的sqlSource屬性中。
????????3.解析mapper.xml中的各種標簽的過程中會對已經解析過的xml對應的mapper進行保存,保存在Configuration的mapperRegistry的konwnMappers中,key是接口全限定名,value是接口對應的MapperProxyFactory類型,保存是為了實例化mapper接口時能獲取到mapper及對應的MapperProxyFactory(作用是實例化時創建mapper接口的代理類MapperProxy)
????????4.mybatis-plus這種,不寫xml的,會解析baseMapper中的方法,根據實體類信息等,生成sql。
實例化SqlSessionTemplate
????????1.構建一個SqlSessionTemplate,用來在實例化mapper接口時獲取mapper以及在執行查詢時獲取sqlsession。SqlSessionTemplate里維護DefaultSqlSessionFactory,比如獲取Configuration就會通過SqlSessionTemplate獲取DefaultSqlSessionFactory然后在獲取Configuration,查詢時獲取和數據庫關聯的sqlsession,也是通過SqlSessionTemplate維護的DefaultSqlSessionFactory的opensession方法獲取到的,類型是DefaultSqlSession。
實例化mapper
????????掃描mapper文件變成BeanDefinition(@Mapper和@MapperScan),變成BeanDefinition后會把BeanDefinition中的BeanClass屬性設置為MapperFactoryBean類型,以便在spring容器實例化對象時,對mapper接口也進行實例化,也就是生成對應的代理類MapperProxy,用以執行mapper的增刪改查方法。
????????實例化完這三個對象,springboot就可以等待前端調用接口然后執行mapper方法進行增刪改查了。

2.查詢時

????????也就是前端調接口,然后調service,然后調mapper的方法時做的工作

解析傳參
????????當通過servcie調用mapper接口的方法時,會調用代理對象MapperProxy的invoke 方法。然后會調用MapperMethod的invoke 方法。在MapperMethod的invoke 方法會調用MapperMethod的execute方法。在這個方法中會調用SqlSessionTemplate的對應方法執行查詢,在調用之前會進行方法參數解析,最終方法是ParamNameResolver類的getNamedParams,得到一個map,key是參數名,value是參數值。
獲取最終的sql
????????mapper.xml中的sql會在MybatisAutoConfiguration中構建SqlSessionFactory時得到解析,如果有where if之類的標簽會被解析成DynamicSqlSource,如果是普通的查詢語句(select * from departments where department_id=#{depId})則會被解析成RawSqlSource,這個屬性會被存在configuration的mappedstatements屬性中,屬性名稱為sqlSource。然后執行查詢時,會從sqlSource中拿到對應的原始sql,然后再進行解析,也就是把方法調用時的傳參拼接到sql中以及拼接where if這種動態標簽,最終得到完整的sql。方法就是對應的SqlSource類的getBoundsql方法。這里DynamicSqlSource類的getBoundsql方法也會調用RawSqlSource的getBoundSql方法。

3.查詢后

也就是查詢出結果后做的工作

  1. 解析返回值:關鍵類DefaultResultSetHandler,基本邏輯都是在這個類實現的。關鍵類ResultSetWrapper,保存要映射的字段集合和查詢出的數值的字節數組
  2. DefaultResultSetHandler類的handleResultSets方法,先拿到需要映射的字段集合,封裝在ResultSetWrapper中,然后再獲取一個resultmap類型的集合,每個resultmap保存需要映射的類型,如果有resultmap標簽則會封裝到resultmappings屬性中。
  3. DefaultResultSetHandler類的handleResultSet方法,調用handleRowValues方法處理結果集放到multipleResults中。
  4. DefaultResultSetHandler類的handleRowValues方法,分別處理嵌套映射和非嵌套映射。
  5. 非嵌套映射,handleRowValuesForSimpleResultMap方法,遍歷映射每行數據,調用getRowValue方法。沒加resultmap或者resultmap中沒做映射的字段調用applyAutomaticMappings方法,resultmap中映射的字段調用applyPropertyMappings方法。具體方法就是調用對應字段類型的typeHandler從字節數組中拿到數據進行轉換。所有對應字段值的字節數組在ResultSetWrapper的resultset屬性中。
  6. 嵌套映射,handleRowValuesForNestedResultMap方法,遍歷映射每行數據,調用重載的getRowValue方法,沒加resultmap或者resultmap中沒做映射的字段調用applyAutomaticMappings方法,resultmap中映射的字段調用applyPropertyMappings方法,嵌套映射的字段調用applyNestedResultMappings方法。而applyNestedResultMappings會再次調用getRowValue方法解析每行數據,邏輯和非嵌套映射相同。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/44967.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/44967.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/44967.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue3 + Echarts堆疊折線圖的tooltip不顯示問題

問題介紹 使用Echarts在Vue3Vite項目中繪制堆疊折線圖的的時候&#xff0c;tooltip總是不顯示&#xff0c;經過很長時間的排查和修改&#xff0c;最后發現是在使用上有錯誤導致的。 錯誤圖片展示 問題原因 由于Vue3底層使用proxy代理創建示例&#xff0c;使用其創建出來的實…

RDD 專項練習

RDD 專項練習 現有分數信息文件 scores.txt 班級ID 姓名 年齡 性別 科目 成績 12 張三 25 男 chinese 50 12 張三 25 男 math 60 12 張三 25 男 english 70 12 李四 20 男 chinese 50 12 李四 20 男 math 50 12 李四 20 男 english 50 12 王芳 19 女 chinese 70 12 王芳 19 女…

FPGA-Verilog-Vivado-軟件使用

這里寫目錄標題 1 軟件配置2 FPGA-7000使用2.1 運行啟動方式 1 軟件配置 編輯器綁定為Vscode&#xff0c;粘貼VS code運行文件的目錄&#xff0c;后綴參數保持不變&#xff1a; 如&#xff1a; D:/Users/xdwu/AppData/Local/Programs/Microsoft VS Code/Code.exe [file name]…

從技術到管理:你必須知道的七個轉變

在職業生涯的道路上&#xff0c;很多技術骨干會逐步轉向管理崗位。這不僅是職位的晉升&#xff0c;更是角色、思維和能力的全方位轉變。以下是七個關鍵的轉變&#xff0c;幫助技術人員順利完成這一跨越。 一、從個人貢獻者到團隊領導者的轉變 在技術崗位上&#xff0c;成功往…

(19)夾鉗(用于送貨)

文章目錄 前言 1 常見的抓手參數 2 參數說明 前言 Copter 支持許多不同的抓取器&#xff0c;這對送貨應用和落瓶很有用。 按照下面的鏈接&#xff08;或側邊欄&#xff09;&#xff0c;根據你的設置了解配置信息。 Electro Permanent Magnet v3 (EPMv3)Electro Permanent M…

bug記錄 qInstallMessageHandler的使用

QT (純C)項目 ‘Qxxx‘ file not found 和 編譯報錯問題(已解決)_qt頭文件file not found-CSDN博客 qInstallMessageHandler&#xff08;指針函數參數&#xff09; 需要靜態指針&#xff0c;這個函數 #include <iostream> #include "singleton.h" #include &…

Linux操作系統CentOS如何更換yum鏡像源

簡介 CentOS&#xff0c;是基于Red Hat Linux提供的可自由使用源代碼的企業級Linux發行版本&#xff1b;是一個穩定&#xff0c;可預測&#xff0c;可管理和可復制的免費企業級計算平臺。 下載地址: centos安裝包下載_開源鏡像站-阿里云 相關倉庫&#xff1a; CentOS過期源&…

職業教育人工智能實驗實訓室建設應用案例

隨著人工智能技術的快速發展&#xff0c;其在職業教育領域的應用逐漸深入。唯眾作為一家專注于教育技術領域的企業&#xff0c;積極響應國家關于人工智能教育的政策號召&#xff0c;通過建設人工智能實驗實訓室&#xff0c;為學生提供了一個實踐操作與創新思維相結合的學習平臺…

C++ STL iter_swap用法和實現

一&#xff1a;功能 交換兩個迭代器指向的元素值&#xff0c;一般用在模板中 二&#xff1a;使用 #include <vector> #include <iostream>template <typename It, typename Cond>requires std::forward_iterator<It> && std::indirectly_swa…

富格林:曝光糾正安全交易誤區

富格林指出&#xff0c;貴金屬投資是許多投資者追求資產多樣化和風險管理的重要途徑。然而&#xff0c;正如任何投資領域一樣&#xff0c;不少投資者也對貴金屬投資產生了一些誤區和錯誤觀念。但事實上&#xff0c;如果這種誤區一直伴隨著我們的交易進程&#xff0c;是很難做到…

34 超級數據查看器 關聯圖片

超級數據查看器app&#xff08;excel工具&#xff0c;數據庫軟件&#xff0c;表格app&#xff09; 關聯圖片講解 點擊 打開該講的視頻 點擊訪問app下載頁面 豌豆莢 下載地址 大家好&#xff0c;今天我們講一下超級數據查看器的關聯圖片功能 這個功能能讓表中的每一條信息&…

數據結構-散列表(hash table)

6.1 散列表的概念 散列表又叫哈希&#xff08;hash&#xff09;表&#xff0c;是根據鍵&#xff08;key&#xff09;直接訪問在內存存儲位置的值&#xff08;value&#xff09;的數據結構&#xff0c;由數組演化而來&#xff08;根據數組支持按照下標進行隨機訪問數據的特性&a…

windows腳本獲取 svn版本號

簡介 需要使用項目中svn的最新版本號 命令 set svnURL"URL" svn info %svnURL% | findstr "Revision:" > Version.txt for /f "token2 delims " %%i in (Version.txt) do set rev%%i echo %rev% pause

力扣爆刷第163天之TOP100五連刷81-85(回文鏈表、路徑和、最長重復子數組)

力扣爆刷第163天之TOP100五連刷81-85&#xff08;回文鏈表、路徑和、最長重復子數組&#xff09; 文章目錄 力扣爆刷第163天之TOP100五連刷81-85&#xff08;回文鏈表、路徑和、最長重復子數組&#xff09;一、234. 回文鏈表二、112. 路徑總和三、169. 多數元素四、662. 二叉樹…

洛谷 B4006 [GESP202406 四級] 寶箱

題目描述 小楊發現了 &#x1d45b; 個寶箱&#xff0c;其中第 &#x1d456; 個寶箱的價值是 &#x1d44e;&#x1d456;?。 小楊可以選擇一些寶箱放入背包并帶走&#xff0c;但是小楊的背包比較特殊&#xff0c;假設小楊選擇的寶箱中最大價值為 &#x1d465;&#xff0c…

next input代碼嘗試編寫

使用有限狀態機&#xff08;FSM&#xff09;可以使代碼結構更清晰&#xff0c;特別是處理復雜的狀態和過渡時。以下是如何根據你提供的步驟&#xff0c;用有限狀態機來實現自動校準和中斷觸發邏輯的示例代碼。 狀態定義 IDLE: 空閑狀態&#xff0c;等待數據輸入。CALIBRATING…

Python高級(三)_正則表達式

Python高級-正則表達式 第三章 正則表達式 在開發中會有大量的字符串處理工作,其中經常會涉及到字符串格式的校驗。 1、正則表達式概述 正則表達式,又稱正規表示式、正規表示法、正規表達式、規則表達式、常規表示法(英語:Regular Expression,在代碼中常簡寫為regex、…

PostgreSql中的JSON數據類型

PostgreSQL 提供了兩種 JSON 數據類型&#xff1a;JSON 以及 JSONB。這兩種類型主要的區別在于數據存儲格式&#xff0c;JSONB 使用二進制格式存儲數據&#xff0c;更易于處理。 PostgreSQL 推薦優先選擇 JSONB 數據類型。 兩種數據類型之間的區別&#xff1a; 功能JSONJSONB存…

網絡建設與運維23國賽網絡運維正式賽題解析

競賽環境請看主頁&#xff01; 23國賽網絡運維 任務描述&#xff1a;某集團公司在更新設備后&#xff0c;路由之間無法正常通信&#xff0c;請修 復網絡達到正常通信。 &#xff08;1&#xff09; 請在server1“管理員”下拉菜單中選擇“鏡像”選項卡&#xff0c;點 擊 “創…

超聲波眼鏡清洗機有用嗎?四大主流超聲波清洗機品牌整理測評

長期佩戴的眼鏡&#xff0c;若不定期清洗&#xff0c;不僅鏡片會逐漸積聚油脂、灰塵&#xff0c;影響透光率&#xff0c;使視物模糊&#xff0c;更嚴重的是&#xff0c;眼鏡上日益增加的微小雜質和細菌可能會逐漸影響到眼睛健康&#xff0c;導致視力下降、眼部疾病等問題。 這…