成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

解讀 MyBatis 源碼:探尋數據持久化的奧秘

開發
們將逐步揭開 MyBatis 源碼那神秘的面紗,去追尋它高效數據處理、靈活映射機制以及出色性能表現背后的根源。

在當今軟件開發的廣袤領域中,MyBatis 作為一款備受青睞的持久層框架,以其強大的功能和靈活的特性發揮著重要作用。當我們深入探究 MyBatis 的源碼時,就如同開啟一扇通往技術奧秘的大門。

在這里,每一行代碼都蘊含著智慧與巧思,每一個模塊都承載著獨特的設計理念。我們將逐步揭開 MyBatis 源碼那神秘的面紗,去追尋它高效數據處理、靈活映射機制以及出色性能表現背后的根源。通過對其源碼的仔細剖析,我們不僅能更深刻地理解 MyBatis 是如何工作的,更能汲取其中的精髓,為我們自身的技術成長和項目實踐提供寶貴的經驗和啟示。讓我們懷揣著對技術的好奇與探索之心,正式踏上 MyBatis 源碼解析的精彩旅程……

詳解Mybatis的功能架構與核心技術

按照工作層次劃分可以分為三層:

  • 接口層:也就是我們用戶用到的這一層,提供各種對數據的CRUD以及配置信息維護的API調用。
  • 數據處理層:這層是框架為上層提供的關鍵,這一層實現參數映射,SQL解析,SQL執行,結果處理。
  • 基礎支撐層:負責連接管理、配置加載,事務管理、緩存機制等。

詳解Mybatis執行過程

本質上mybatis執行過程大體是:

  • 參數映射
  • sql解析
  • sql執行
  • 結果和處理映射

我們以下面這段查詢代碼為例,針對該流程進行深入講解:

Tb1Example tb1Example = new Tb1Example();
        tb1Example.createCriteria().andBirthdayIsNull();
        List<Tb1> tb1List = SpringUtil.getBean(Tb1Mapper.class).selectByExample(tb1Example);
        log.info("tb1List: {}", tb1List);

本質上我們所使用的Tb1Mapper是基于我們的xml配置動態代理生成的一個MapperProxy,在執行查詢請求時被本質上就調用這個生成代理對象,以我們的selectByExample為例,在初始配置的時候我們指明了select標簽在進行代理創建時該方法就會被標準為SELECT命令請求,執行時就會按照代理的查詢邏輯執行。

隨后代理的MapperProxy會調用MapperMethod進行參數解析,將參數轉換為后續可拼接到xml中所配置sql語句中的參數。

然后SqlSessionTemplate通過內部sqlSessionProxy的selectList著手進行實際查詢工作,其內部會拿到當前sql連接的session和xml中配置的sql還有我們上述步驟的參數生成jdbc的Statement然后通過SimpleExecutor執行sql查詢,然后通過resultSetHandler將結果解析并返回:

對此我們也給出相應的源碼,首先從MapperProxy開始調用mapperMethod進行參數解析。

//MapperProxy的invoke方法調用mapperMethod
@Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

//MapperMethod解析參數并基于指令匹配SQL操作
```java
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      //......
      }
      case UPDATE: {
         //......
      }
      case DELETE: {
        //......
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          //......
        } else if (method.returnsMany()) {
        //內部進行參數解析和查詢調用
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          //......
        } else {
          //......
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
   //......
    return result;
  }

隨后步入MapperMethod進行通過convertArgsToSqlCommandParam參數解析,底層在基于sqlSession著手查詢和結果轉換:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //參數解析
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      //
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
   //......
    return result;
  }

最終來到SimpleExecutor的doQuery方法,通過xml配置所得的各種信息生成StatementHandler創建出Statement ,再通過resultSetHandler處理結果并返回:

@Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
    //通過配置信息生成StatementHandler 
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //基于StatementHandler 生成Statement 
      stmt = prepareStatement(handler, ms.getStatementLog());
      //內部通過Statement 執行sql并將結果交由resultSetHandler轉換并返回
      return handler.query(stmt, resultHandler);
    } finally {
      //......
    }
  }

為什么Mybatis不需要實現類

是通過代理生成的,我們不妨通過源碼來看看究竟,以下面這段代碼作為入口講解原生mapper創建思路:

SqlSession sqlSession = SpringUtil.getBean(SqlSessionFactory.class).openSession();
 Tb1Mapper mapper = sqlSession.getMapper(Tb1Mapper.class);

本質上getMapper會基于接口和sqlSession信息通過mapper創建工廠mapperProxyFactory ,然后mapperProxyFactory 底層通過反射的方式創建JDK動態代理mapper對象:

對此我們給出getMapper的入口,邏輯和筆者說的一樣mapperProxyFactory 傳遞元信息進行動態代理創建:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
   //......
   
    try {
    //通過mapperProxyFactory創建動態代理
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

查看newInstance方法即可看到我們所說的基于類加載器、接口信息和methodCache內部的MapperMethodInvoker完成動態代理對象的創建:

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

如何實現Mybatis插件

Mybatis支持對ParameterHandler、ResultSetHandler、StatementHandler、Executor進行攔截,例如我們想對mybatis查詢的SQL結果解析階段進行攔截,我們可以編寫下面這樣一段代碼:

import java.sql.Statement;

//@Intercepts({@Signature(
//        type = Executor.class,  //確定要攔截的對象
//        method = "query",        //確定要攔截的方法
//        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}   //攔截方法的參數
//)})

@Intercepts({@Signature(
        type = ResultSetHandler.class,  //確定要攔截的對象
        method = "handleResultSets",        //確定要攔截的方法
        args = {Statement.class}   //攔截方法的參數
)})
public class MyInterceptor implements Interceptor {


    private static Logger logger = LoggerFactory.getLogger(MyInterceptor.class);


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("請求被攔截,攔截類:[{}],請求方法:[{}]請求參數[{}]", invocation.getTarget().getClass().getName(),
                invocation.getMethod().getName(),
                invocation.getArgs());
        //如果當前代理的是一個非代理對象,那么就會調用真實攔截對象的方法
        // 如果不是它就會調用下個插件代理對象的invoke方法
        Object obj = invocation.proceed();
        logger.info("請求被攔截結果:[{}]", obj);
        return obj;
    }
}

然后配置文件,增加對這個攔截類的配置:

<plugins>
        <plugin interceptor="com.sharkchili.mapper.MyInterceptor">
            <property name="dbType"  value="mysql"/>
        </plugin>
    </plugins>

執行我們的請求:

// 可以從配置或者直接編碼來創建SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //2)通過SqlSessionFactory創建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //3)通過sqlsession執行數據庫操作
        User1Mapper user1Mapper = sqlSession.getMapper(User1Mapper.class);
        User1 user = user1Mapper.select("1");

        logger.info("查詢結果:[{}]", user.toString());

        if (sqlSession != null) {
            sqlSession.close();
        }

從輸出結果就可以看出,我們的方法攔截到了結果處理的邏輯了。

2022-11-30 10:11:55,389 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==>  Preparing: select * from user1 where id = ?
2022-11-30 10:11:55,526 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - ==> Parameters: 1(String)
[main] INFO com.sharkchili.mapper.MyInterceptor - 請求被攔截,攔截類:[org.apache.ibatis.executor.resultset.DefaultResultSetHandler],請求方法:[handleResultSets]請求參數[[org.apache.ibatis.logging.jdbc.PreparedStatementLogger@12d2ce03]]
2022-11-30 10:12:06,928 [main] DEBUG [com.sharkchili.mapper.User1Mapper.select] - <==      Total: 1
[main] INFO com.sharkchili.mapper.MyInterceptor - 請求被攔截結果:[[User1{id='1', name='小明', user2=null}]]
[main] INFO com.sharkchili.mapper.MyBatisTest - 查詢結果:[User1{id='1', name='小明', user2=null}]
2022-11-30 10:12:06,938 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@24959ca4]

Mybatis插件的工作原理

Mybatis如何引入自定義插件?

我們的業務代碼如下,創建SqlSessionFactory:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

步入build邏輯會看到xml解析相關代碼有一行,如下所示,可以看出他就是對xml文件中plugins標簽進行解析:

//獲取標簽內容并反射生成攔截器存到某個list中
this.pluginElement(root.evalNode("plugins"));

其內部做的就是解析xml配置,生成攔截器對象MyInterceptor,并存放到interceptorChain中的一個名為interceptors的list中。

private void pluginElement(XNode parent) throws Exception {
        if (parent != null) {
            Iterator var2 = parent.getChildren().iterator();

            while(var2.hasNext()) {
            //解析配置生成Interceptor 
                XNode child = (XNode)var2.next();
                String interceptor = child.getStringAttribute("interceptor");
                Properties properties = child.getChildrenAsProperties();
                Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).getDeclaredConstructor().newInstance();
                interceptorInstance.setProperties(properties);
                //存到攔截器鏈中
                this.configuration.addInterceptor(interceptorInstance);
            }
        }

    }

我們看看addInterceptor,邏輯非常簡單,說白了就是存到一個名為interceptors的list集合中,然后進行鏈式調用:

public void addInterceptor(Interceptor interceptor) {
 
        this.interceptorChain.addInterceptor(interceptor);
    }

執行真正邏輯,調用插件:

//3)通過sqlsession執行數據庫操作
        User1Mapper user1Mapper = sqlSession.getMapper(User1Mapper.class);
        User1 user = user1Mapper.select("1");

注意在Plugin的signatureMap插個斷點,如下所示:

這時候進行debug,我們可以看到堆棧中停在這樣一段代碼上。由于我們編寫了一個結果解析的攔截插件MyInterceptor,所以在newResultSetHandler時會從上文注冊的interceptorChain中取出對應處理器,給我們的resultSetHandler ,這過程中piugin類會通過Plugin.wrap(target, this);對我們的結果處理類進行包裝。

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
//獲取我們的結果解析器
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        //調用pluginAll將所有插件都引入
        ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }

所以我們步入看看wrap的邏輯,可以看到interceptor即我們自己編寫的插件,他會通過getSignatureMap獲取這個我們編寫插件MyInterceptor注解上的信息,通過反射生成一個新的代理對象,這個對象存放著signatureMap。

public static Object wrap(Object target, Interceptor interceptor) {
//獲取自定義插件信息存到signatureMap 中
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        // 使用插件包裝我們的目標類
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

執行SQL邏輯:

最終我們的結果處理插件會在handleResultSets階段發現signatureMap里面有值,當前處理器有攔截,執行this.interceptor.intercept(new Invocation(this.target, method, args))攔截相關處理器執行我們的MyInterceptor邏輯。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
        //發現signatureMap有值
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            //methods 中包含我們的這個階段的方法handleResultSets,故調用this.interceptor.intercept(new Invocation(this.target, method, args))
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

Mybatis的分頁插件的實現原理

通過上面工作原理的介紹我們就知道原理了,分頁插件就是通過mybatis提供的接口,攔截Executor的query方法,重寫執行的sql,例如select * from student,攔截sql后重寫為:select t.* from (select * from student) t limit 0, 10;即可。

責任編輯:趙寧寧 來源: 寫代碼的SharkChili
相關推薦

2010-02-03 16:15:05

Python語言

2010-01-15 10:22:24

C++語言

2011-07-07 09:12:46

智慧運算WatsonPower

2023-10-27 13:59:30

Mybatis占位符

2020-08-26 10:25:16

智慧

2010-12-01 14:37:00

2010-02-04 11:06:14

2023-10-08 08:22:33

2021-03-18 08:18:15

ZooKeeper數據持久化

2022-03-18 15:55:15

鴻蒙操作系統架構

2021-07-26 05:33:59

自動化領導CIO

2018-12-14 09:48:23

Redis數據故障

2021-01-21 08:49:52

數據單體架構

2023-08-17 16:17:00

Docker前端

2017-09-21 08:16:33

數據存儲環境

2017-08-17 09:46:42

大數據諸葛io數據挖掘

2011-07-10 16:04:01

程序員

2023-09-08 08:42:01

數據場景項目

2021-09-01 07:21:40

ArrayPool源碼Bucket
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲国产精品一区二区久久 | 亚洲免费一区 | av毛片免费| 亚洲人成人一区二区在线观看 | 日韩成人av在线 | 亚洲综合大片69999 | 色必久久 | 欧美日韩国产三级 | 久久99久久98精品免观看软件 | 一级欧美 | 五月天婷婷丁香 | 国产精品久久久乱弄 | 国产区视频在线观看 | 综合中文字幕 | 99久久精品视频免费 | 女人毛片a毛片久久人人 | 免费国产黄网站在线观看视频 | 伊人二区 | 国产精品综合一区二区 | 午夜私人影院在线观看 | 国产三级 | 午夜精品久久久 | 色视频在线观看 | 毛片的网址 | 国产精品一区在线播放 | 国产精品69av| 国产精品久久性 | 啪啪免费网 | 久久99深爱久久99精品 | 色综合天天综合网国产成人网 | 精品一区二区在线看 | 成人午夜视频在线观看 | 电影在线| 久一精品| 国产精品永久久久久久久www | 99热.com| 精品少妇一区二区三区日产乱码 | 99re视频在线观看 | 国产美女一区二区 | 91免费在线 | 成人av在线大片 |