Mybatis源碼分析


一、Mybatis環境快速入門

1、maven依賴

<dependencies>
    <!-- mybatis核心包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.3.0</version>
    </dependency>
    <!-- mysql驅動包 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.29</version>
    </dependency>
    <!-- junit測試包 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2、創建mybatis配置文件 configuration

<?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"/>
            <!-- 數據庫連接相關配置 ,這里動態獲取config.properties文件中的內容-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mapping文件路徑配置 -->
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>

</configuration>

3、Mapper配置文件

<?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,namespace的值習慣上設置成包名+sql映射文件名,這樣就能夠保證namespace的值是唯一的 例如namespace="com.mayikt.mapper.UserMapper"就是com.mayikt.mapper(包名)+userMapper(userMapper.xml文件去除后綴) -->
<mapper namespace="com.mayikt.mapper.UserMapper">
    <!-- 在select標簽中編寫查詢的SQL語句, 設置select標簽的id屬性為getUser,id屬性值必須是唯一的,不能夠重復 使用parameterType屬性指明查詢時使用的參數類型,resultType屬性指明查詢返回的結果集類型 resultType="com.mayikt.entity.User"就表示將查詢結果封裝成一個User類的對象返回 User類就是users表所對應的實體類 -->
    <!-- 根據id查詢得到一個user對象 -->
    <select id="getUser" parameterType="int" resultType="com.mayikt.entity.UserEntity"> select * from user where id=#{id} </select>
</mapper>

4、實體類

public class UserEntity { private Integer id; private Date birdate; private String name; }

5、mapper接口

public interface UserMapper { public UserEntity getUser(int id); }

6、運行Mybatis代碼

public class TestMyBatis { public static void main(String[] args) { try { //配置文件
            String configXml = "mybatis_config.xml"; //加載配置獲取流
            Reader reader = Resources.getResourceAsReader(configXml); //獲取sqlSessionFactory工廠
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); //獲取sqlSession
            SqlSession sqlSession = sqlSessionFactory.openSession(); //獲取對應的mapper
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //執行方法
            UserEntity user = userMapper.getUser(1); System.out.println("name:" + user.getName()); } catch (IOException e) { e.printStackTrace(); } } }

7、數據表結構

CREATE TABLE `user` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

二、Mybatis核心配置文件

1、Properties(屬性)

    Java屬性文件可以配置直觀的。

如:

<properties>
        <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql:///mybatis"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="root"/>
</properties>

或者通過直接引入屬性文件,例如:

<properties resource="db.properties"></properties>

然后db.properties文件中的配置就是:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///mybatis
jdbc.username=root jdbc.password=root

2、typeAliases(類型別名)

類型別名是Java類型的簡稱。

逐個設置,例如:

<typeAliases>

   <typeAlias type="com.mayikt.entity.User" alias="user"/>

</typeAliases>

3、Plugins

分頁插件配置

四、Mybatis大體架構流程分析

1、流程圖

      1、讀取resources獲取對應的Reader對象。

            reader = Resources.getResourceAsReader(resources);

       2、使用SqlSessionFactoryBuilder獲取SqlSessionFactory源碼分析

           SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader); 

源碼分析:

  1. Reader reader = Resources.getResourceAsReader(resources);

       調用javaioAPI  讀取resources配置文件,獲取InputStreamReader

     2、SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

  3、使用XMLConfigBuilder 解析mybatis配置文件

SqlSessionFactoryBuilder使用XMLConfigBuilder解析配置文件,封裝成Configuration對象。

4、因為在構造函數設置了parsed 為fasle,XMLConfigBuilder 只能被使用一次。

調用該方法解析mybatis_config文件

通過反射機制匹配接口

注意:XMLConfigBuilder運行之后,只能被解析一次 否則會拋出異常。

xml轉換程bean對象 configuration

XMLConfigBuilder的作用是:解析mybatis配置文件文件 得到configuration

XMLMapperBuilder的作用是什么: 解析mybatisMapper文件

建議很多源碼中設置值都是采用構造函數形式

loadedResource 存放都是mybatis映射的文件路由地址 使用set集合存放

1.protected final Set<String> loadedResources = new HashSet<String>();

注意:mapper文件配置的namespace一定要和接口對應 否則情況查找失敗!

2mapperRegistry作用存放dao層mapper接口 底層使用過map集合存放。

 

解析配置文件完成了之后,都會裝配到configuration

Configuration作用:mybatis核心的配置文件內容 ,使用xml轉換bean

Mybatis掃包方式有兩種一種是 寫package、和resource

 

明白了 mapperRegistry 注冊我們的Mapper文件。

使用configuration獲取默認的DefaultSqlSessionFactory

五、MybatisMapper接口綁定原理

Mapper既然是接口,沒有被初始化如何被調用的?

答案: 使用動態代理技術

public static void main(String[] args) throws IllegalAccessException, InstantiationException { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); UserMapper userMapper = getMapper(UserMapper.class); UserEntity user = userMapper.getUser(1); System.out.println("user:" + user.toString()); } //1.獲取對應的Mapper接口
public static <T> T getMapper(Class<T> clas) throws IllegalArgumentException, InstantiationException, IllegalAccessException { return (T) Proxy.newProxyInstance(clas.getClassLoader(), new Class[]{clas}, new MyBatisJdkInvocationHandler(clas)); }
public class MyBatisJdkInvocationHandler implements InvocationHandler { /** * 目標對象 */
    private Object target; public MyBatisJdkInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return new UserEntity(1000l, "螞蟻課堂", 20); } /** * 獲取代理對象接口 * * @param <T> * @return
     */
    public <T> T getProxy() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }

接口綁定源碼分析:

解析節點信息封裝到Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(),然后添加到configuration里面

轉換成Java類就是一個MappedStatement(存sql信息)

使用Configuration的getMappedStatement方法來獲取MappedStatement對象

獲取的方式key的組成為命名空

 getMapper接口

從mapperRegistry獲取查詢接口對應的綁定接口

1、檢查是否已經注冊過Mapper接口
2、使用MapperProxyFactory 創建代理類MapperProxy

 

 

UserMapper.getUser方法的時候 調用MapperProxy的invoke方法

因為mapperRegister中 key:mapper接口 value MapperProxyFactory
使用MapperProxyFacotory創建MapperProxy代理
Mybatis基於多個不同的接口生成代理類 不同接口肯定不同的invoke方法
相同的接口,不同的方法肯定是走同一個invoke方法。

UserMapper.getUser()執行原理分析
1、調用MapperPrxoxy的invoke方法()
2、實現接口方法與配置文件sql語句關聯

底層使用的是:result = sqlSession.selectOne(command.getName(), param);

大致原理分析:

  SqlSession提供select/insert/update/delete方法,在舊版本中使用使用SqlSession接口的這些方法,但是新版的Mybatis中就會建議使用Mapper接口的方法。

 射器其實就是一個動態代理對象,進入到MapperMethod的execute方法就能簡單找到SqlSession的刪除、更新、查詢、選擇方法,

從底層實現來說:通過動態代理技術,讓接口跑起來,之后采用命令模式,最后還是采用了SqlSession的接口方法(getMapper()方法等到Mapper)執行SQL查詢

(也就是說Mapper接口方法的實現底層還是采用SqlSession接口方法實現的)。

五、總結:

  1. 獲取本地InputStreamReader對象(mybatis配置文件)
  2. 調用SqlSessionFactoryBuilder 
  3. ###在使用XMLConfigBuilder解析mybatis配置文件,裝配到Configuration中。
  4. 將配置文件中的Mapper添加到Configuration mapperRegistry實現注冊。

        備注:mapperRegistry存放當前所有的mapper文件。

       5.使用Configuration獲取默認的DefaultSqlSessionFactory

六、MybatisMapper SQLSession源碼分析

1、SQLSession的作用

SqlSession提供select/insert/update/delete方法,在舊版本中使用使用SqlSession接口的這些方法

每個線程都應該有它自己的 SqlSession 實例。SqlSession 的實例不是線程安全的,因此是不能被共享的。

 

2、Executor執行器原理分析

1.openSessionFromDataSource,首先是從 Configuration 中取出相關的配置,生成 Transaction,接着又創建了一個 Executor,最后返回了 DefaultSqlSession 對象。

2.SimpleExecutor: 默認的 Executor,每個 SQL 執行時都會創建新的 Statement

ResuseExecutor: 相同的 SQL 會復用 Statement

BatchExecutor: 用於批處理的 Executor

CachingExecutor: 可緩存數據的 Executor,用代理模式包裝了其它類型的 Executor

默認情況下使用緩存的CachingExecutor

創建的openSession源碼分析:
1、創建事務管理器
2、創建執行器、
默認是創建簡單的執行器會變成緩存執行器呢
最好交給DefaultSqlSession
默認創建SimpleExecutor執行器 ,判斷是否開啟二級緩存,如果開啟了二級緩存
CachingExecutor執行器構造函數傳遞SimpleExecutor

 如果二級緩存沒有,走簡單執行器

3、SelectOne底層原理查詢分析

  1. 當查詢單條數據的時候,最終還是調用selectList查詢多個結果集包裝程單個對象。

 

  1. 從configuration中獲取到MappedStatement(對應的sql語句配置),調用executor的query方法實現執行。

  先查詢二級緩存,是否有緩存,沒有的話調用delegate.<E> query

如果一級緩存中沒有該結果,會調用queryFromDatabase查詢數據庫得到數據讓后在緩存到一級緩存中,下次查詢的時候相同的sql語句直接走一級緩存不會查詢數據庫。

一級(sqlSession緩存)和二級緩存(sessionFactory)

為什么CachingExecutor需要找到SimpleExecutor創建緩存key呢? 方便實現緩存key代碼重構

mybatis緩存控制 先查找二級緩存(硬盤、redis)、二級緩存沒有的情況在查找一級緩存。

一級緩存絕對是有的 ,但是二緩存可以沒有。

PerpetualCache 指的就是我們的一級 一級緩存屬於本地緩存 存放在內存中 使用map集合存放

4、Mybatis一級與二級緩存

一級緩存實現原理

相同查詢sql語句和參數

第一次查詢的時候 會調用數據庫的查詢 ,緩存到本地內存中
第二次查詢的時候 直接走本地內存 不會查詢數據庫。

sqlSession緩存為了防止臟數據,增加、修改、刪除的時候 都會清楚所有本地一級緩存。

mybatis的一級緩存是SqlSession級別的緩存,在操作數據庫的時候需要先創建SqlSession會話對象,

在對象中有一個HashMap用於存儲緩存數據,此HashMap是當前會話對象私有的,別的SqlSession會話對象無法訪問。

具體流程:

1.第一次執行select完畢會將查到的數據寫入SqlSession內的HashMap中緩存起來

2.第二次執行select會從緩存中查數據,如果select相同切傳參數一樣,那么就能從緩存中返回數據,不用去數據庫了,從而提高了效率

注意事項:

  1. 如果SqlSession執行了DML操作(insert、update、delete),並commit了,那么mybatis就會清空當前SqlSession緩存中的所有緩存數據,

           這樣可以保證緩存中的存的數據永遠和數據庫中一致,避免出現臟讀

  1. 當一個SqlSession結束后那么他里面的一級緩存也就不存在了,mybatis默認是開啟一級緩存,不需要配置
  2. mybatis的緩存是基於[namespace:sql語句:參數]來進行緩存的,意思就是,SqlSession的HashMap存儲緩存數據時,是使用[namespace:sql:參數]作為key

注意:服務器集群的時候,每個sqlSession有自己獨立的緩存相互之間不存在共享,所以在服務器集群的時候容易產生數據沖突問題。

一級存在那些問題呢? 線程安全問題

一級緩存不共享 二級緩存存在共享

配置以下配置可以實現開啟日志打印

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="cacheEnabled" value="false"/>
</settings>

如何禁止一級緩存

方案1  在sql語句上 隨機生成 不同的參數 存在缺點:map集合可能爆 內存溢出的問題

方案2  開啟二級緩存

方案3  使用sqlSession強制清除緩存

方案4  創建新的sqlSession連接。

二級緩存SessionFactory

 二級緩存是mapper級別的緩存,也就是同一個namespace的mappe.xml,當多個SqlSession使用同一個Mapper操作數據庫的時候,得到的數據會緩存在同一個二級緩存區域,二級緩存默認是沒有開啟的。

需要在setting全局參數中配置開啟二級緩存

Config.配置

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

在UserMapper配置

<!-- 以下兩個<cache>標簽二選一,第一個可以輸出日志,第二個不輸出日志 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" />
<!-- <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> -->

二級緩存回收策略

 LRU:最近最少使用的策略,移除最長時間不被使用的對象。

 FIFO:先進先出策略,按對象進入緩存的順序來移除它們。

 SOFT:軟引用策略,移除基於垃圾回收器狀態和軟引用規則的對象。

 WEAK:弱引用策略,更積極地移除基於垃圾收集器狀態和弱引用規則的對象。

 軟引用與弱引用的區別:

  軟引用: 軟引用是用來描述一些有用但並不是必需的對象, 對於軟引用關聯着的對象,只有在內存不足的時候JVM才會回收該對象

  弱引用: 弱引用也是用來描述非必需對象的,當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象

TransactionalCache

TransactionalCache:繼承自Cache接口,主要作用是保存SqlSession在事務中需要向某個二級緩存提交的緩存數據(因為事務過程中的數據可能會回滾,所以不能直接把數據就提交二級緩存,而是暫存在TransactionalCache中,在事務提交后再將過程中存放在其中的數據提交到二級緩存,如果事務回滾,則將數據清除掉)

TransactionalCacheManager

TransactionalCacheManager:用於管理CachingExecutor使用的二級緩存對象,只定義了一個transactionalCaches字段

private final Cache delegate; //對應的二級緩存對象

private boolean clearOnCommit; //是否在commit時清除二級緩存的標記

// 需要在commit時提交到二級緩存的數據

private final Map<Object, Object> entriesToAddOnCommit;

// 緩存未命中的數據,事務commit時,也會放入二級緩存(key,null)

private final Set<Object> entriesMissedInCache;

StatementHandler

StatementHandler接口的實現大致有四個,其中三個實現類都是和JDBC中的Statement響對應的:
SimpleStatementHandler,這個很簡單了,就是對應我們JDBC中常用的Statement接口,用於簡單SQL的處理; 存在sql注入攻擊問題

PreparedStatementHandler,這個對應JDBC中的PreparedStatement,預編譯SQL的接口;

防止sql注入

CallableStatementHandler,這個對應JDBC中CallableStatement,用於執行存儲過程相關的接口;

RoutingStatementHandler,這個接口是以上三個接口的路由,沒有實際操作,只是負責上面三個StatementHandler的創建及調用。

ResultSetHandler

就是將Statement實例執行之后返回的ResultSet結果集轉換成我們需要的List結果集

一級緩存與二級緩存區別

①、一級緩存是SqlSession級別的緩存。在操作數據庫時需要構造sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。

不同的sqlSession之間的緩存數據區域(sqlHashMap)是互相不影響的。

②、二級緩存是mapper級別的緩存,多個SqlSession去操作同一個Mapper的語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。

注意:sqlSession緩存底層存在線程安全問題。

 

七、Mybatis使用常用設計模式

 

Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;

 

工廠模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;

 

單例模式,例如ErrorContext和LogFactory;

 

代理模式,Mybatis實現的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態代理;還有executor.loader包使用了cglib或者javassist達到延遲加載的效果;

 

組合模式,例如SqlNode和各個子類ChooseSqlNode等;

 

模板方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;

 

適配器模式,例如Log的Mybatis接口和它對jdbc、log4j等各種日志框架的適配實現;

 

裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現;

 

迭代器模式,例如迭代器模式PropertyTokenizer;

八、流程圖總結

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM