您現在的位置是:首頁 > 綜合
如何深度理解mybatis?
- 由 黑馬程式設計師 發表于 綜合
- 2022-12-12
首先要經過什麼時間
深度自定義mybatis
回顧mybatis的操作的核心步驟
編寫核心類SqlSessionFacotryBuild進行解析配置檔案
深度分析解析SqlSessionFacotryBuild乾的核心工作
編寫核心類SqlSessionFacotry
深度分析解析SqlSessionFacotry乾的核心工作
編寫核心類SqlSession
深度分析解析SqlSession乾的核心工作
總結自定義mybatis用的技術點
一。 回顧mybatis的操作的核心步驟
宣告一點我們本篇主要探討的是mybatis的註解方式的操作, 完全從頭開始都是小編從頭開搞的, 如果與其他大神的程式碼思維有出入請多指教
我們首先需要準備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”>
準備好結果的實體類以及在mapper介面上編寫需要執行的sql語句
public class User { private Integer uid; private String username; private String password; private String nickname;}
package cn。itcast。mapper;import cn。itcast。pojo。User;import org。apache。ibatis。annotations。Select;import java。util。List;public interface UserMapper { @Select(“select * from users”) List
使用mybatis的api來幫助我們完成sql語句的執行以及結果集的封裝
//1。關聯主配置檔案InputStream in = Resources。getResourceAsStream(“mybatis-config。xml”);//2。解析配置檔案SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = builder。build(in);//3。建立會話物件SqlSession sqlSession = sqlSessionFactory。openSession();//4。可以採用介面代理的方式UserMapper mapper = sqlSession。getMapper(UserMapper。class);List
思考: mybatis大致是如何幫我們完成相關操作的 ?
我們透過Resources的getResourceAsStream告訴了mybatis我們編寫的核心配置檔案的位置, mybatis就可以找到我們資料庫的連線資訊, 也同時找到我們編寫的sql語句的地方, 然後可以將其解析按照某種規則存放起來, 我們透過呼叫介面代理的方式執行方法時, 可以找到對應方法上的sql語句然後執行將結果封裝返回給我們
二。 編寫核心類SqlSessionFacotryBuild進行解析配置檔案
那麼我們廢話不多說開始我們自定義mybatis的旅程,
1。首先我們需要使用者編寫配置檔案, 然後透過我們自己的Resources來告訴我們配置檔案所在位置
package com。itheima。ibatis。configuration;import java。io。InputStream;public class Resources { public static InputStream getResourceAsStream(String path) { return ClassLoader。getSystemClassLoader()。getResourceAsStream(path); }}
2。 然後需要定義SqlSessionFacotryBuild來對配置檔案進行解析分發
package com。itheima。ibatis。configuration;import com。itheima。ibatis。core。session。SqlSessionFactory;import com。itheima。ibatis。core。session。impl。DefaultSqlSessionFactory;import org。dom4j。Document;import org。dom4j。DocumentException;import org。dom4j。Element;import org。dom4j。Node;import org。dom4j。io。SAXReader;import javax。sql。DataSource;import java。io。File;import java。io。InputStream;import java。lang。reflect。Method;import java。util。List;import java。util。Properties;public class SqlSessionFactoryBuilder { private Configuration configuration = new Configuration(); public SqlSessionFactory build(InputStream in) { SAXReader saxReader = new SAXReader(); Document document = null; try { document = saxReader。read(in); } catch (DocumentException e) { e。printStackTrace(); } Element rootElement = document。getRootElement(); parseEnvironment(rootElement。element(“environments”)); parseMapper(rootElement。element(“mappers”)); return new DefaultSqlSessionFactory(configuration); } private void parseMapper(Element mapper) { String pack = mapper。element(“package”)。attributeValue(“name”); String directory = pack。replace(“。”, “/”); String path = ClassLoader。getSystemClassLoader()。getResource(“”)。getPath(); File mapperDir = new File(path, directory); if (!mapperDir。exists()) { throw new RuntimeException(“找不到mapper對映”); } findMapper(mapperDir, pack); // System。out。println(configuration。getSql()); } private void findMapper(File mapperDir, String base) { File[] files = mapperDir。listFiles(); if (files != null) { for (File file : files) { if (file。isFile()) { if (file。getName()。endsWith(“。class”)) { String name = file。getName(); name = name。substring(0, name。lastIndexOf(“。”)); String className = base + “。” + name; initMapper(className); } } else { findMapper(file, base + “。” + file。getName()); } } } } private void initMapper(String className) { try { Class<?> clazz = Class。forName(className); Method[] methods = clazz。getMethods(); for (Method method : methods) { if(method。getAnnotations()。length>0){ Mapper mapper = ParseMapper。parse(method); this。configuration。getMappers()。put(className + “。” + method。getName(), mapper); } } } catch (Exception e) { e。printStackTrace(); } } private void parseEnvironment(Element environments) { String defEnv = environments。attributeValue(“default”); Node node = environments。selectSingleNode(“//environment[@id=‘” + defEnv + “’]”); List
三。 深度分析解析SqlSessionFacotryBuild乾的核心工作
1。 build(InputStream in) 方法做的工作
①藉助Dom4j的來解析了xml檔案, 將environments解析工作分發給了parseEnvironment(Element environments)
②將mappers的解析工作分發給了parseMapper(Element mapper)
2。 parseEnvironment(Element environments)方法做的工作
①主要解析了連線資料庫的引數們, 並且建立了資料庫連線池
自定義連線池非本章節的重點,所以這裡內部本質採用的Druid連線池來做了簡化
package com。itheima。ibatis。configuration;import com。alibaba。druid。pool。DruidDataSourceFactory;import javax。sql。DataSource;import java。util。Properties;public class DefaultDataSource { public DataSource getDataSource(Properties properties) { try { return DruidDataSourceFactory。createDataSource(properties); } catch (Exception e) { e。printStackTrace(); } return null; }}
②將解析好的連線池放入configuration物件中,mappers成員變數先別糾結下一章節會講解
package com。itheima。ibatis。configuration;import lombok。Data;import javax。sql。DataSource;import java。util。HashMap;import java。util。Map;@Datapublic class Configuration { private Map
詳細圖解如下圖
3。parseMapper(Element mapper) 方法做的工作
①解析出使用者配置的package找到sql語句所在介面的資料夾, 交給initMapper來處理
②遞迴找到這個包下所有的。class檔案,並且獲取到介面的全類名, 然後交給initMapper來處理
③initMapper透過反射獲取類中的每一個方法,將方法交給一個專門解析方法上的註解的工具類ParseMapper的parse方法處理,處理完後將其放到configuration中的mappers的集合中
④ParseMapper的parse方法做的工作, 這是解析配置的核心地方
package com。itheima。ibatis。configuration;import java。lang。annotation。Annotation;import java。lang。reflect。InvocationTargetException;import java。lang。reflect。Method;import java。lang。reflect。ParameterizedType;import java。lang。reflect。Type;import java。util。ArrayList;import java。util。List;import java。util。regex。Matcher;import java。util。regex。Pattern;public class ParseMapper { public static Mapper parse(Method method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Annotation[] annotations = method。getAnnotations(); Object value = annotations[0]。getClass()。getMethod(“value”)。invoke(annotations[0]); Mapper mapper = new Mapper(); Class<?> resultType = method。getReturnType(); String val = (String) value; Pattern pattern = Pattern。compile(“\\#\\{\\s*\\w+\\s*\\}”); Matcher matcher = pattern。matcher(val); List
首先拿到方法上的註解,得到使用者填入的sql語句
然後處理sql語句#{引數}的這些資料, 然後將引數的順序儲存起來, 用來後期設定引數的資料做準備, 一個
方法對應一個Mapper物件
然後再根據結果型別, 判斷是什麼型別相關的操作,方便後期執行對應的sql語句
四。 編寫核心類SqlSessionFacotry
1。回顧那個地方建立的SqlSessionFacotry物件
經過SqlSessionFacotryBuilder的努力, 我們成功的將配置檔案中核心的資訊解析出來並放入了configuration物件中了, 然後我們此時將解析好的configuration傳入到SqlSessionFacotry中
SqlSessionFactory的實現類如下:
public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; private TransactionManagement defaultTransactionManagement; public DefaultSqlSessionFactory(Configuration configuration) { this。configuration =configuration; defaultTransactionManagement = new DefaultTransactionManagement(configuration。getDataSource()); } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration,defaultTransactionManagement,false); }}
2。新增事務管理器
事務管理是一個小的功能, 裡面希望使用ThreadLocal集合來保證一個使用者拿到的連結是同一個
事務管理的程式碼如下:
public class DefaultTransactionManagement implements TransactionManagement { private ThreadLocal
五。 深度分析解析SqlSessionFacotry乾的核心工作
1。 SqlSession openSession() 方法做的工作
可以看的出來我們在這個方法建立了DefaultSqlSession物件,並傳入封裝好的configuration,預設的事務管理器
預設透過openSession事務是開啟的等等相關的引數
六。編寫核心類SqlSession
其實有SqlSession的介面,我們使用的實現類是DefaultSession, 這裡記錄瞭解析的配置物件configuration
預設事務管理器物件transactionManagement, 預設事務開啟的狀態tx標記
package com。itheima。ibatis。core。session。impl;import com。itheima。ibatis。configuration。Configuration;import com。itheima。ibatis。configuration。Mapper;import com。itheima。ibatis。core。BaseExecutor;import com。itheima。ibatis。core。annotation。Param;import com。itheima。ibatis。core。session。SqlSession;import com。itheima。ibatis。core。transaction。TransactionManagement;import java。lang。reflect。*;import java。util。HashMap;import java。util。List;import java。util。Map;public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final boolean tx; private TransactionManagement transactionManagement; public DefaultSqlSession(Configuration configuration, TransactionManagement transactionManagement, boolean tx) { this。configuration = configuration; this。transactionManagement = transactionManagement; this。tx = tx; } public void close() { transactionManagement。close(); } @Override public void commit() { transactionManagement。commit(); } @Override public void rollback() { transactionManagement。rollback(); } @Override public
七。深度分析解析SqlSession乾的核心工作
1。selectOne & selectList做的工作
主要是分發了下功能, 執行sql語句避免不了有引數和無引數的, 都讓呼叫有引數的方便管理
在執行前, 考慮還有一種情況, 使用者不是透過介面代理的方式來執行以上方法, 這樣手動輸入sqlId容易造成錯誤
這裡做一個健壯性判斷
BaseExecutor中的query以及queryList做的核心工作
首先這兩個方法的特點都是查詢, 其步驟基本類似, 所以這裡可以合併一起轉調query0功能
這裡需要對引數進行設定, 還根據最後isOne的引數決定返回值是否是單個
引數設定這裡比較複雜我們透過圖解的方式來解釋, (注: 引數是List集合型別的和陣列型別的沒有做!!!)
對結果的封裝主要用到內省技術和資料庫元資料等等知識點
2。update&delete&insert做的工作
BaseExecutor中的update做的核心工作
還是和query&queryList一樣需要設定引數, 不管是增刪改其本質其結果都是一致
3。getMapper代理模式開發的原理
主要使用的動態代理的技術建立介面的實現類, 內部主要整合了sqlId和引數, 省去使用者自己拼sqlId拼錯的風險
也同時解決使用者手動合引數的麻煩, 但是最終工作的還是selectOne,selectList以及update0這些方法
總結自定義mybatis用的技術點
一款框架的誕生肯定不是一蹴而就的, 隨著時間慢慢推進逐步更新出來, 所以一款好的框架肯定要經過
很多考驗才能夠穩定靠譜, 但是縱觀整篇用的技術點, 不難發現框架也是由基礎程式碼編寫而來,解決大量重複
的工作, 提供擴充套件性等等機制,比如本篇用核心的技術點有
① 反射
② 內省
③ 解析xml
④ 動態代理
⑤ 工廠設計模式
等等, 感謝大家耐心閱覽, 附件有本篇的原碼, 如果有更好的建議和想法歡迎和小編一起探討交流