眾所周知,MyBatis
是對##JDBC進行封裝而成的產品,所以,聊聊MyBatis原始碼之前我們得先了解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);
Statement或
PreparedStatement物件:
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如果用到我们项目中基本上都会存在以下几个问题:
针对上面这些问题,于是一大堆持久化框架应运而生。
做持久层的框架有很多,有orm
系和utils
系列。
orm
系列的代表有:hibernate
,eclipseLink
,topLink
;
新加入開發的朋友,估計不知道MyBatis 的前身,在2010年之前,不交MyBatis
,叫做ibatis
。
MyBatis
是一款優秀的持久性層框架,它支援客製化 SQL、預存程序以及進階映射。 MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定參數以及取得結果集。 MyBatis
可以使用簡單的XML 或註解來配置和映射原生訊息,將介面和Java 的POJOs
(Plain Ordinary Java Object,普通的Java物件)映射成資料庫中的記錄。
需要来源两个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 ('1', 'tian', '19', '1');
创建该数据库表的实体类:
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&characterEncoding=utf8&autoReconnect=true&useSSL=false&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
接口,這裡需要呼叫SqlSession
的getMapper()
方法建立一個動態代理對象,然後呼叫UserMapper
代理實例的方法即可完成與資料庫的互動。
針對上面這個案例,我們來整理一下MyBatis
的整體執行流程和核心元件。
#用於描述MyBatis
的主配置訊息,其他元件需要取得設定資訊時,直接透過Configuration
物件取得。除此之外,MyBatis在應用程式啟動時,將Mapper配置資訊、型別別名、TypeHandler
等註冊到Configuration
元件中,其他元件需要這些資訊時,也可以從Configuration
物件中取得。
MappedStatement用於描述Mapper中的SQL配置訊息,是對Mapper XML
設定檔中<select|update|delete|insert>
等標籤或@Select/@Update
等註解配置資訊的封裝。
#SqlSession
是MyBatis提供的使用者導向的API,表示和資料庫互動時的會話對象,用於完成資料庫的增刪改查功能。 SqlSession
是Executor元件的外觀,目的是對外提供易於理解和使用的資料庫操作介面。
Executor
是MyBatis的SQL執行器,MyBatis中資料庫所有的增刪改查運算都是由Executor組件完成的。
StatementHandler
封裝了對JDBC Statement
物件的操作,例如為Statement物件設定參數,呼叫Statement介面提供的方法與資料庫交互,等等。
當MyBatis
框架所使用的Statement類型為CallableStatement
和 PreparedStatement
時,ParameterHandler
用於為Statement物件參數佔位符設定值。
#ResultSetHandler
封裝了對JDBC中的ResultSet物件操作,執行SQL類型為SELECT語句時,ResultSetHandler用於將查詢結果轉換成Java物件。
TypeHandler
是MyBatis中的類型處理器,用於處理Java類型與JDBC類型之間的對應。它的作用主要體現在能夠根據Java類型呼叫PreparedStatement
或CallableStatement
物件對應的setXXX()
方法為Statement物件設定值,而且能夠根據Java類型呼叫ResultSet物件對應的getXXX()
取得SQL執行結果。
使用JDBC API
開發應用程序,其中一個比較煩瑣的環節是處理JDBC類型與Java類型之間的轉換。涉及Java類型和JDBC型別轉換的兩種情況如下:
PreparedStatement
物件為參數佔位符設定值時,需要呼叫PreparedStatement
介面中提供的一系列的setXXX()
方法,將Java類型轉換為對應的JDBC類型並為參數佔位符賦值。 ResultSet
物件後,需要呼叫ResultSet物件的getXXX()
方法取得欄位值,此時會將JDBC類型轉換為Java類型。 MyBatis
提供的TypeHandler
#及與Java類型和JDBC類型之間的對應關係:
我們使用到了SqlSession
元件,它是使用者層面的API。實際上SqlSession
是Executor元件的外觀,目的是為使用者提供更友善的資料庫操作接口,這是設計模式中外觀模式的典型應用。
真正執行SQL操作的是Executor元件,Executor可以理解為SQL執行器,它會使用StatementHandler
元件對JDBC的Statement物件進行操作。
當Statement類型為CallableStatement
和PreparedStatement
時,會透過ParameterHandler元件為參數佔位符賦值。 ParameterHandler
元件中會根據Java類型找到對應的TypeHandler
對象,TypeHandler中會透過Statement
物件提供的setXXX()
方法(例如setString()方法)為Statement物件中的參數佔位符設定值。
StatementHandler
元件使用JDBC中的Statement物件與資料庫完成互動後,當SQL語句類型為SELECT時,MyBatis透過ResultSetHandler
#元件從Statement物件中取得ResultSet對象,然後將ResultSet物件轉換為Java對象。
在MyBatis 中大量的使用了設計模式,在MyBatis 我們可以學到幾個設計模式:
一級快取和
二級快取所構成,這兩層快取都是使用 Cache 介面的實作類別。因此,在接下裡的章節中,我會先向大家介紹 Cache 幾種實作類別的源碼,然後再分析一級和二級快取的實作。
一級快取是在Executor中實現的。 MyBatis的Executor元件有3種不同的實現,分別為SimpleExecutor
、ReuseExecutor
和BatchExecutor
。這些類別都繼承自BaseExecutor
,在BaseExecutor類別的query()方法中,首先從快取中取得查詢結果,如果取得不到,則從資料庫中查詢結果,然後將查詢結果快取起來。而MyBatis的二級緩存則是透過裝飾器模式實現的,當透過cacheEnabled參數開啟了二級緩存,MyBatis框架會使用CachingExecutor對SimpleExecutor
、ReuseExecutor
或 BatchExecutor
進行裝飾,當執行查詢操作時,對查詢結果進行緩存,執行更新操作時則更新二級快取。本章最後介紹了MyBatis
如何整合Redis作為二級快取。
除此之外,MyBatis也支援Ehcache
、OSCache
等,這種特性並不常用。
大多數框架,都支援插件,使用者可透過編寫插件來自行擴充功能,Mybatis也不例外。
MyBatis提供了擴充機制,能夠在執行Mapper時改變SQL的執行行為。這種擴展機制是透過攔截器來實現的,用戶自訂的攔截器也被稱為MyBatis插件。
MyBatis框架支援對Executor
、ParameterHandler
、ResultSetHandler
、StatementHandler
四種元件的方法進行攔截。掌握了MyBatis插件的實作原理,然後自己實作一個分頁查詢外掛程式和慢SQL統計插件,當我們需要的功能MyBatis框架無法滿足時,可以考慮透過自訂外掛程式來實現。
經典實作:PageHelper 。
MyBatis針對不同的日誌框架提供對Log介面對應的實現,Log介面的實作類別下圖所示。從實作類別可以看出,MyBatis支援7種不同的日誌實現,具體如下。
下面對常用幾個做一個簡單說明:
MyBatis找出日誌框架的順序為
SLF4J→JCL→Log4j2→Log4j→JUL→No Logging。
如果Classpath下不存在任何日誌框架,則使用NoLoggingImpl日誌實作類,即不輸出任何日誌。
動態SQL指的是事先無法預知特定的條件,需要在執行時間根據具體的情況動態地產生SQL語句。
在MyBatis中有著豐富的動態SQL標籤,例如:<where>
、<if>
、<choose|when|otherwise> ;
、<foreach>
等。
在MyBatis原始碼中有個SqlSource物件會作為MappedStatement物件的屬性保存在MappedStatement物件中。執行Mapper時,會根據傳入的參數資訊呼叫SqlSource物件的getBoundSql()方法取得BoundSql對象,這個過程就完成了將SqlNode物件轉換為SQL語句的過程。
例如:,#{}
佔位符會被替換為“?”,然後呼叫JDBC中PreparedStatement物件的setXXX()方法為參數佔位符設定值,而$ {}佔位符則會直接替換為傳入的參數文字內容。
#本文是對MyBatis原始碼分析的整體感受,希望對你有點幫助。
以上是一週學完MyBatis源碼,萬字總結的詳細內容。更多資訊請關注PHP中文網其他相關文章!