Mybatis源碼剖析
基礎環境搭建
-
JDK8
-
Maven3.6.3(別的版本也可以…)
-
MySQL 8.0.28 --> MySQL 8
-
Mybatis 3.4.6
-
準備jar,準備數據庫數據
把依賴導入pom.xml
中
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target></properties><dependencies><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!-- <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency>--><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.8.1</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.5</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.6</version></dependency><!--<dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.27.0-GA</version></dependency><dependency><groupId>ognl</groupId><artifactId>ognl</artifactId><version>3.2.18</version></dependency>--><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.0.3</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version></dependency><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version></dependency><dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>3.1</version></dependency></dependencies><build></build>
數據庫中放置倆張表
CREATE TABLE `t_user` (`id` int DEFAULT NULL,`name` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
往里面加入數據
CREATE TABLE `t_account` (`id` int DEFAULT NULL,`accountNo` varchar(255) DEFAULT NULL,`balance` double DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
往里面加入數據
2. 準備配置文件
a. 基本配置文件 mybatis-config.xml
- 數據源的設置 environments
- 類型別名
- mapper文件的注冊
<?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><!-- <settings><setting name="cacheEnabled" value="true"/></settings>--><typeAliases><typeAlias type="com.baizhiedu.entity.User" alias="User"/><typeAlias type="com.baizhiedu.entity.Account" alias="Account"/></typeAliases><!-- <plugins>-->
<!-- <!–<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor">-->
<!-- <property name="test" value="111111"/>-->
<!-- </plugin>–>-->
<!-- <!–<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor2"/>–>-->
<!-- <!–<plugin interceptor="com.baizhiedu.plugins.MyMybatisInterceptor3"/>–>-->
<!-- <!– <plugin interceptor="com.baizhiedu.plugins.PageHelperInterceptor1">-->
<!-- <property name="queryMethodPrefix" value="query"/>-->
<!-- <property name="queryMethodSuffix" value="ByPage"/>-->
<!-- </plugin>–>-->
<!--<!– <plugin interceptor="com.baizhiedu.plugins.LockInterceptor"/>–>-->
<!-- </plugins>--><environments default="default"><environment id="default"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"></property><property name="url" value="jdbc:mysql://localhost:3306/suns?useSSL=false"></property><property name="username" value="root"></property><property name="password" value="123xxx"></property></dataSource></environment><!-- <environment id="oracle"><transactionManager type="JDBC"></transactionManager><dataSource type="POOLED"><property name="driver" value="oracle.jdbc.OracleDriver"></property><property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"></property><property name="username" value="hr"/><property name="password" value="hr"/></dataSource></environment>--></environments><mappers><!--<package name=""--><mapper resource="UserDAOMapper.xml"/><mapper resource="AccountDAOMapper.xml"/></mappers></configuration>
-默認IDEA配置,MySQL環境搭建大家都懂,略過,不會的可以關注私聊評論-
Mybatis回顧
1. Mybatis做什么?
Mybatis是一個ORM類型框架,解決的數據庫訪問和操作的問題,對現有JDBC技術的封裝。
2. 核心代碼分析
首先看看項目結構
interface
public interface AccountDAO {public void save(Account account);
}
public interface UserDAO {//public void save(User user); //SqlSession.insert()public void save(User user);public List<User> queryAllUsersByPage();//SqlSesson.select()public User queryUserById(@Param("id") Integer id);public void update(User user);}
entity
package com.baizhiedu.entity;import java.io.Serializable;public class Account implements Serializable {private Integer id;private String accountNo;private double balance;public Account() {System.out.println("---------account----------");}public Account(Integer id, String accountNo, double balance) {this.id = id;this.accountNo = accountNo;this.balance = balance;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getAccountNo() {return accountNo;}public void setAccountNo(String accountNo) {this.accountNo = accountNo;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}@Overridepublic String toString() {return "Account{" +"id=" + id +", accountNo='" + accountNo + '\'' +", balance=" + balance +'}';}
}
package com.baizhiedu.entity;import java.io.Serializable;public class User implements Serializable {private Integer id;private String name;private Integer version;public User() {
}public User(Integer id, String name) {this.id = id;this.name = name;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getVersion() {return version;}public void setVersion(Integer version) {this.version = version;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}
}
現在讓我們測試一下是否可以查到數據
import com.baizhiedu.dao.UserDAO;
import com.baizhiedu.entity.User;
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 org.junit.Test;import java.io.IOException;
import java.io.InputStream;public class TestMybatis {// 方式一@Testpublic void test1() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();UserDAO userDAO = sqlSession.getMapper(UserDAO.class);User user = userDAO.queryUserById(4);System.out.println(user);}// 方式二@Testpublic void test2() throws IOException {InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession = sqlSessionFactory.openSession();// UserDAO userDAO = sqlSession.getMapper(UserDAO.class);// User user = userDAO.queryUserById(4);//System.out.println( "類型是" + sqlSession.selectOne("com.baizhiedu.dao.UserDAO.queryUserById", 4).getClass());User user = (User)sqlSession.selectOne("com.baizhiedu.dao.UserDAO.queryUserById", 4);System.out.println(user);}}
測試類中,哪一種方法好?
功能 :兩種方式功能等價
實現效果: 都有耦合性,更改sql字段,倆者都需要更改
那種方式好:第一種方式好 表達概念更清晰 ,第一種封裝定義類型,第二種字符串不能表示類型,就好比 String name = "張三"
和 new User().getName()
第一個可以表示人,也可以表示狗,但第二個表示人清晰可見
第一種開發,本質上就是對第二種開發的封裝。(代理設計模式)后續再聊
Mybatis核心對象
大家先看這張圖有個印像
對于我們來講。我們在對mybatis進行定義或者進行初步接觸的過程當中,我們一直且反復都在強調的一個概念是什么呢?就是mybatis
它是一個JDBC的封裝。它是通過什么來進行的封裝?它是通過sqlsession這個對象。來進行的封裝。那封裝的是什么呢?那既然封裝的是JDBC,那JDBC無外乎也就會涉及到這么幾個核心的類型,一個是connection。一個是statement,一個是resultset。所以在這塊兒呢,我們自然而然的就會得到這樣一個信息,就是mybits這個框架。
通過sqlsession封裝了JDBC。那封裝了JDBC的連接connection。封裝了statement,當然這個statement就包括我們所說的三種statement,一種是最普通的statement。一種是預編譯statement,一種是coablestatement,而coablestatement,它主要應用在哪呢?主要應用在存儲過程層面上。那通過這些statement與我們的數據庫進行交互,最后它的結果由result進行封裝,進而返回給我。所以circlesession它實際上應該封裝的是這些東西。那這是我們最初在接觸mybatis的時候,
給大家的一個最最基本的概念。但是如果我們仔細分析的話,你就會發現,作為mybatis來講,它其實不僅僅包括sqlsession。它還包括什么呢?它還包括sqlsession。前面的什么?他的父親,也就是他的工廠。sqlsessionfactory.它還包括什么?還包括mybatis-config.xml以及我們的mapper.xml這些東西。所以你如果僅認為它封裝了JDBC進行使用的話,那實際上理解上是沒有問題的。但是細節有很多的偏差。那它至少要包括的是四個環節。sqlsession封裝了JDBC的使用。而它還提供了sqlsession factory來創建sqlsession。那還需要我們在配置文件當中去書寫相關的配置,進而最終由sqlsession幫我們基于mapper文件。生成dao。哎,那么這一套東西才構成了mybatis,所以顯然我們曾經的分析是不到位的,是不透徹的。是有問題的。
它實際上是有兩大類核心對象的一類,我們叫做數據存儲類對象。一類我們叫做的是操作類型的對象。哎,這是整個mybatis的兩塊兒內容。那什么是數據存儲類的對象呢?它的概念是什么呢?它最為核心的概念就是在JAVA中。或者說,在虛擬機當中。對。mybatis.相關的配置信息。進行封裝。因為我們知道文件,它存了很多東西。它存了很多配置的內容,我們不可能用點兒就讀一次文件,用點兒就讀一次文件,因為什么呢?因為它會頻繁產生IO。而你要知道,作為IO來講,它是操作系統層面上的資源,它的一個創建絕不是虛擬機單獨來完成的。它一定是要虛擬機來與操作系統進行交互和交流來完成的。所以注定IO在我們的開發過程當中一定是越少越好,能復用最好。那所以我們說這些mybatis的相關的配置信息,它不可能是隨用隨讀的,它一定是一次性讀取。進而封裝在JAVA的對象當中。這是火星。能聽得明白我的意思嗎?好,那這就涉及到了兩個問題了,哪兩個問題呢?第一個問題就是它的配置信息要封裝對象,它有幾種配置信息呢?兩種一種,剛才我們說過了,叫做mybatis-config.xml,另外一種,我們叫做xxxdaomapper.xml。
由這兩個。那這兩種信息最終都要進行JAVA的封裝,那么這個mybatis-config.xml封裝成了什么呢?封裝成了一個叫做configuration的對象。那換句話說,我們可以認為configuration對象。它封裝的就是mybatis相關的內容呃。而這個xxxDaomapper.xml,它對應的是怎么進行的封裝呢?它對應的是一個叫做mappedstatement。對象的風格。當然這塊兒僅僅是一個形象上的認知。呃,那不準確。后面呢,我們還會再剖析。那所以呢,首先第一個層面上,我們就要去驗證,什么驗證我們所說的。這個configuration這個類是對mybatis.config.xml的封裝。那怎么來驗證呢?
這個是開啟二級緩存,我們后續會繼續剖析
1. 數據存儲類對象概念:在Java中(JVM)對Mybatis相關的配置信息進行封裝mybatis-config.xml ----> ConfigurationConfiguration 1. 封裝了mybatis-config.xml2. 封裝了mapper 文件 MappedStatement3. 創建Mybatis其他相關的對象 XXXDAOMapper.xml ----> MappedStatement(形象的認知,不準確)操nt對象 對應的就是 Mapper文件中的一個一個的 配置標簽 <select id. -----> MappedStatement<insert id. -----> MappedStatement 注定 一個Mybatis應用中 N個MappedStament 對象 MappedStatment ---> Configuration MappedStatment 中 封裝SQL語句 ---> BoundSql
2. 操作類對象 (SqlSession) ---> 門面 ExcutorExcutor 是Mybatis中處理功能的核心1. 增刪改update 查query2. 事務操作 提交 回滾3. 緩存相關的操作Excutor接口 (適配器模式) 操作相關都要設計成接口BatchExcutorJDBC中批處理的操作, BatchExcutor ReuseExcutor目的:復用 Statement (需要sql一樣)insert into t_user(ID,name)values(1,‘孫帥’);insert into t_user(ID,name)values(2,‘孫帥1’);SimpleExcutor常用Excutor Mybatis推薦 默認 Configuration protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;StatmentHandlerStatementHandler是Mybatis封裝了JDBC Statement,真正Mybatis進行數據庫訪問操作的核心功能:增刪改差StatementHandler接口SimpleStatementHandlerJDBC 操作 PreparedStatementHandlerCallableStatementHandler ParameterHandler目的:Mybatis參數 ---》 JDBC 相關的參數 @Param ---> #{} --- > ?ResultSetHandler目的:對JDBC中查詢結果集 ResultSet 進行封裝 TypeHandlerJava程序操作 數據庫Java類型 數據庫類型String varcharint numberint int excutor和statementhandler都用到了適配器模式
在configuration里面,它是不是專門有這么一個內容?是來存所有的mappedstatement的。也就是configuration是可以找到誰的。是可以找到所有的mappedstatement的。那同樣按照我們剛才所關注的mappedstatement里面是不是也存了configuration啊?那也就是mappedstatement是不是也可以找到對應的configuration,因為configuration只是一個,所以它就存了一個,所以它們兩個人的關系是什么是?是雙向的關聯關系,你中有我,我中有你,我既可以通過configuration找到所有的mappedstatement。
那么當然,我也可以通過mappedstatement找到對應的configuration,這樣的話它會方便后續mybatis內部在運行的過程當中。可以去解決一些核心的問題。所以。在這兒你一定要注意,它封裝的是這些標簽,那這些標簽\的內容是和mybatis的mappedstatement一一對應的,而且哎。在一個mybatis應用當中,它可以有n個mappedstatement,并且mappedstatement.它是可以找到什么呢?
可以找到configuration。這樣我們就把mybatis當中所涉及到的所有的配置文件的數據通過。這兩個類型徹底都封裝完成了。那換句話說,日后你想要這些相關的內容,比如說mybatis-config.xml,想要所有的mappedstatement。和其他相關的對象,你用configuration就可以了,你要想獲得某一個具體的標簽,它相關的內容你是不是有map pedstatement對象就夠了?而且他們彼此是可以互相找到對方的,那你在編程的時候靈活度就更高了。這就是我們所說的在mybatis核心對象當中的第一類對象數據存儲類對象。那這也就是在整個我的這張圖里面。這塊的內容。任何一個mybatis應用都有configuration和n個mappedstatement,而每一個mappedstatement,它對應的就是一個一個的標簽至此,核心對象數據存儲這塊的內容,我就給大家分析完了。當然,這塊還是死的。就是比如說什么時候創建configuration?什么時候創建mappedstatement以及這些數據和mybatis核心的功能,它該怎么交互啊?這都是我們后續要講解的內容。