您現在的位置是:首頁 > 綜合

如何深度理解mybatis?

  • 由 黑馬程式設計師 發表于 綜合
  • 2022-12-12
簡介getName(), mapper)

首先要經過什麼時間

深度自定義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”> <!——資料庫連線資訊——> <!—— 配置sql語句編寫的位置 ——>

準備好結果的實體類以及在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 findAll();}

使用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 all = mapper。findAll();System。out。println(all);//5。釋放資源sqlSession。close();

思考: 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 list = node。selectNodes(“//property”); Properties properties = new Properties(); for (Element element : list) { String name = element。attributeValue(“name”); String value = element。attributeValue(“value”); properties。put(name, value); } DataSource dataSource = new DefaultDataSource()。getDataSource(properties); configuration。setDataSource(dataSource); }}

三。 深度分析解析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 mappers = new HashMap<>(); private DataSource dataSource;}

詳細圖解如下圖

如何深度理解mybatis?

3。parseMapper(Element mapper) 方法做的工作

①解析出使用者配置的package找到sql語句所在介面的資料夾, 交給initMapper來處理

如何深度理解mybatis?

②遞迴找到這個包下所有的。class檔案,並且獲取到介面的全類名, 然後交給initMapper來處理

如何深度理解mybatis?

③initMapper透過反射獲取類中的每一個方法,將方法交給一個專門解析方法上的註解的工具類ParseMapper的parse方法處理,處理完後將其放到configuration中的mappers的集合中

如何深度理解mybatis?

④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 paramNames = new ArrayList<>(); while (matcher。find()) { String group = matcher。group(); String fieldName = group。substring(2, group。length() - 1)。trim(); paramNames。add(fieldName); } String sql = val。replaceAll(“\\#\\{\\s*\\w+\\s*\\}”, “?”); mapper。setSql(sql); mapper。setParameterNames(paramNames); mapper。setSql(sql); if (resultType == List。class) { mapper。setSelectList(true); Type genericReturnType = method。getGenericReturnType(); ParameterizedType parameterizedType = (ParameterizedType) genericReturnType; Type actualTypeArgument = parameterizedType。getActualTypeArguments()[0]; mapper。setResultType(actualTypeArgument。getTypeName()); mapper。setType(“SELECT”); } else if (resultType == Integer。class || resultType == int。class) { mapper。setType(“UPDATE”); } else { mapper。setType(“SELECT”); mapper。setResultType(resultType。getName()); } return mapper; }}

首先拿到方法上的註解,得到使用者填入的sql語句

如何深度理解mybatis?

然後處理sql語句#{引數}的這些資料, 然後將引數的順序儲存起來, 用來後期設定引數的資料做準備, 一個

方法對應一個Mapper物件

如何深度理解mybatis?

然後再根據結果型別, 判斷是什麼型別相關的操作,方便後期執行對應的sql語句

如何深度理解mybatis?

四。 編寫核心類SqlSessionFacotry

1。回顧那個地方建立的SqlSessionFacotry物件

經過SqlSessionFacotryBuilder的努力, 我們成功的將配置檔案中核心的資訊解析出來並放入了configuration物件中了, 然後我們此時將解析好的configuration傳入到SqlSessionFacotry中

如何深度理解mybatis?

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集合來保證一個使用者拿到的連結是同一個

如何深度理解mybatis?

事務管理的程式碼如下:

public class DefaultTransactionManagement implements TransactionManagement { private ThreadLocal threadLocal = new ThreadLocal<>(); private DataSource dataSource; public DefaultTransactionManagement(DataSource dataSource) { this。dataSource = dataSource; } public Connection getConnection() { Connection connection = threadLocal。get(); if (connection == null) { try { connection = dataSource。getConnection(); } catch (SQLException e) { e。printStackTrace(); } threadLocal。set(connection); } return connection; } @Override public void commit() { Connection connection = threadLocal。get(); if (connection != null ) { try { connection。commit(); } catch (Exception e) { e。printStackTrace(); } } } @Override public void rollback() { Connection connection = threadLocal。get(); if (connection != null) { try { connection。rollback(); } catch (SQLException e) { e。printStackTrace(); } } } public void close() { Connection connection = threadLocal。get(); if (connection != null) { try { connection。close(); threadLocal。remove(); } catch (SQLException e) { e。printStackTrace(); } } } @Override public void begin() { Connection connection = threadLocal。get(); if (connection != null) { try { connection。setAutoCommit(false); } catch (SQLException e) { e。printStackTrace(); } } }}

五。 深度分析解析SqlSessionFacotry乾的核心工作

1。 SqlSession openSession() 方法做的工作

可以看的出來我們在這個方法建立了DefaultSqlSession物件,並傳入封裝好的configuration,預設的事務管理器

預設透過openSession事務是開啟的等等相關的引數

如何深度理解mybatis?

六。編寫核心類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 List selectList(String sqlId) { return selectList(sqlId, null); } @Override public List selectList(String sqlId, Object param) { List list = new BaseExecutor(transactionManagement, tx)。queryList(getMapper(sqlId), param); return (List) list; } @Override public T selectOne(String sqlId) { return selectOne(sqlId, null); } @Override public T selectOne(String sqlId, Object param) { return new BaseExecutor(transactionManagement, tx)。query(getMapper(sqlId), param); } @Override public int delete(String sqlId) { return update0(sqlId, null); } @Override public int delete(String sqlId, Object param) { return update0(sqlId, param); } @Override public int update(String sqlId) { return update0(sqlId, null); } @Override public int update(String sqlId, Object param) { return update0(sqlId, param); } @Override public int insert(String sqlId) { return update0(sqlId, null); } @Override public int insert(String sqlId, Object param) { return update0(sqlId, param); } @Override public T getMapper(Class clazz) { Object o = Proxy。newProxyInstance( clazz。getClassLoader(), new Class[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String sqlId = clazz。getName() + “。” + method。getName(); Mapper mapper = configuration。getMappers()。get(sqlId); String type = mapper。getType(); Object findParam = null; if (args != null) { if (args。length == 1) { Object param = args[0]; boolean isArray = param。getClass()。isArray(); if (!isArray) { findParam = param; } } else { Map map = new HashMap<>(); Parameter[] parameters = method。getParameters(); for (int i = 0; i < parameters。length; i++) { Param param = parameters[i]。getAnnotation(Param。class); String key = “arg”+i; if(param !=null){ key = param。value(); } map。put(key, args[i]); } findParam = map; } } if (type。equals(“SELECT”)) { boolean selectList = mapper。isSelectList(); if (selectList) return selectList(sqlId, findParam); else return selectOne(sqlId, findParam); } else { return update0(sqlId, findParam); } } }); return (T) o; } private int update0(String sqlId, Object param) { return new BaseExecutor(transactionManagement, tx)。update(getMapper(sqlId), param); } public Mapper getMapper(String sqlId) { Mapper mapper = configuration。getMappers()。get(sqlId); if (mapper == null) { throw new RuntimeException(“沒有找到sql對映,請檢查”); } return mapper; }}

七。深度分析解析SqlSession乾的核心工作

1。selectOne & selectList做的工作

主要是分發了下功能, 執行sql語句避免不了有引數和無引數的, 都讓呼叫有引數的方便管理

如何深度理解mybatis?

在執行前, 考慮還有一種情況, 使用者不是透過介面代理的方式來執行以上方法, 這樣手動輸入sqlId容易造成錯誤

這裡做一個健壯性判斷

如何深度理解mybatis?

BaseExecutor中的query以及queryList做的核心工作

首先這兩個方法的特點都是查詢, 其步驟基本類似, 所以這裡可以合併一起轉調query0功能

如何深度理解mybatis?

這裡需要對引數進行設定, 還根據最後isOne的引數決定返回值是否是單個

如何深度理解mybatis?

引數設定這裡比較複雜我們透過圖解的方式來解釋, (注: 引數是List集合型別的和陣列型別的沒有做!!!)

如何深度理解mybatis?

對結果的封裝主要用到內省技術和資料庫元資料等等知識點

如何深度理解mybatis?

2。update&delete&insert做的工作

如何深度理解mybatis?

BaseExecutor中的update做的核心工作

還是和query&queryList一樣需要設定引數, 不管是增刪改其本質其結果都是一致

如何深度理解mybatis?

3。getMapper代理模式開發的原理

主要使用的動態代理的技術建立介面的實現類, 內部主要整合了sqlId和引數, 省去使用者自己拼sqlId拼錯的風險

也同時解決使用者手動合引數的麻煩, 但是最終工作的還是selectOne,selectList以及update0這些方法

如何深度理解mybatis?

如何深度理解mybatis?

如何深度理解mybatis?

總結自定義mybatis用的技術點

一款框架的誕生肯定不是一蹴而就的, 隨著時間慢慢推進逐步更新出來, 所以一款好的框架肯定要經過

很多考驗才能夠穩定靠譜, 但是縱觀整篇用的技術點, 不難發現框架也是由基礎程式碼編寫而來,解決大量重複

的工作, 提供擴充套件性等等機制,比如本篇用核心的技術點有

① 反射

② 內省

③ 解析xml

④ 動態代理

⑤ 工廠設計模式

等等, 感謝大家耐心閱覽, 附件有本篇的原碼, 如果有更好的建議和想法歡迎和小編一起探討交流

Top