偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

解讀 MyBatis 源碼:探尋數(shù)據(jù)持久化的奧秘

開(kāi)發(fā)
們將逐步揭開(kāi) MyBatis 源碼那神秘的面紗,去追尋它高效數(shù)據(jù)處理、靈活映射機(jī)制以及出色性能表現(xiàn)背后的根源。

在當(dāng)今軟件開(kāi)發(fā)的廣袤領(lǐng)域中,MyBatis 作為一款備受青睞的持久層框架,以其強(qiáng)大的功能和靈活的特性發(fā)揮著重要作用。當(dāng)我們深入探究 MyBatis 的源碼時(shí),就如同開(kāi)啟一扇通往技術(shù)奧秘的大門(mén)。

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

詳解Mybatis的功能架構(gòu)與核心技術(shù)

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

  • 接口層:也就是我們用戶用到的這一層,提供各種對(duì)數(shù)據(jù)的CRUD以及配置信息維護(hù)的API調(diào)用。
  • 數(shù)據(jù)處理層:這層是框架為上層提供的關(guān)鍵,這一層實(shí)現(xiàn)參數(shù)映射,SQL解析,SQL執(zhí)行,結(jié)果處理。
  • 基礎(chǔ)支撐層:負(fù)責(zé)連接管理、配置加載,事務(wù)管理、緩存機(jī)制等。

詳解Mybatis執(zhí)行過(guò)程

本質(zhì)上mybatis執(zhí)行過(guò)程大體是:

  • 參數(shù)映射
  • sql解析
  • sql執(zhí)行
  • 結(jié)果和處理映射

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

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

本質(zhì)上我們所使用的Tb1Mapper是基于我們的xml配置動(dòng)態(tài)代理生成的一個(gè)MapperProxy,在執(zhí)行查詢請(qǐng)求時(shí)被本質(zhì)上就調(diào)用這個(gè)生成代理對(duì)象,以我們的selectByExample為例,在初始配置的時(shí)候我們指明了select標(biāo)簽在進(jìn)行代理創(chuàng)建時(shí)該方法就會(huì)被標(biāo)準(zhǔn)為SELECT命令請(qǐng)求,執(zhí)行時(shí)就會(huì)按照代理的查詢邏輯執(zhí)行。

隨后代理的MapperProxy會(huì)調(diào)用MapperMethod進(jìn)行參數(shù)解析,將參數(shù)轉(zhuǎn)換為后續(xù)可拼接到xml中所配置sql語(yǔ)句中的參數(shù)。

然后SqlSessionTemplate通過(guò)內(nèi)部sqlSessionProxy的selectList著手進(jìn)行實(shí)際查詢工作,其內(nèi)部會(huì)拿到當(dāng)前sql連接的session和xml中配置的sql還有我們上述步驟的參數(shù)生成jdbc的Statement然后通過(guò)SimpleExecutor執(zhí)行sql查詢,然后通過(guò)resultSetHandler將結(jié)果解析并返回:

對(duì)此我們也給出相應(yīng)的源碼,首先從MapperProxy開(kāi)始調(diào)用mapperMethod進(jìn)行參數(shù)解析。

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

//MapperMethod解析參數(shù)并基于指令匹配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()) {
        //內(nèi)部進(jìn)行參數(shù)解析和查詢調(diào)用
          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進(jìn)行通過(guò)convertArgsToSqlCommandParam參數(shù)解析,底層在基于sqlSession著手查詢和結(jié)果轉(zhuǎn)換:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //參數(shù)解析
    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;
  }

最終來(lái)到SimpleExecutor的doQuery方法,通過(guò)xml配置所得的各種信息生成StatementHandler創(chuàng)建出Statement ,再通過(guò)resultSetHandler處理結(jié)果并返回:

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

為什么Mybatis不需要實(shí)現(xiàn)類

是通過(guò)代理生成的,我們不妨通過(guò)源碼來(lái)看看究竟,以下面這段代碼作為入口講解原生mapper創(chuàng)建思路:

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

本質(zhì)上getMapper會(huì)基于接口和sqlSession信息通過(guò)mapper創(chuàng)建工廠mapperProxyFactory ,然后mapperProxyFactory 底層通過(guò)反射的方式創(chuàng)建JDK動(dòng)態(tài)代理mapper對(duì)象:

對(duì)此我們給出getMapper的入口,邏輯和筆者說(shuō)的一樣mapperProxyFactory 傳遞元信息進(jìn)行動(dòng)態(tài)代理創(chuàng)建:

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

查看newInstance方法即可看到我們所說(shuō)的基于類加載器、接口信息和methodCache內(nèi)部的MapperMethodInvoker完成動(dòng)態(tài)代理對(duì)象的創(chuàng)建:

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

如何實(shí)現(xiàn)Mybatis插件

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

import java.sql.Statement;

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

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


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


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        logger.info("請(qǐng)求被攔截,攔截類:[{}],請(qǐng)求方法:[{}]請(qǐng)求參數(shù)[{}]", invocation.getTarget().getClass().getName(),
                invocation.getMethod().getName(),
                invocation.getArgs());
        //如果當(dāng)前代理的是一個(gè)非代理對(duì)象,那么就會(huì)調(diào)用真實(shí)攔截對(duì)象的方法
        // 如果不是它就會(huì)調(diào)用下個(gè)插件代理對(duì)象的invoke方法
        Object obj = invocation.proceed();
        logger.info("請(qǐng)求被攔截結(jié)果:[{}]", obj);
        return obj;
    }
}

然后配置文件,增加對(duì)這個(gè)攔截類的配置:

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

執(zhí)行我們的請(qǐng)求:

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

        //2)通過(guò)SqlSessionFactory創(chuàng)建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //3)通過(guò)sqlsession執(zhí)行數(shù)據(jù)庫(kù)操作
        User1Mapper user1Mapper = sqlSession.getMapper(User1Mapper.class);
        User1 user = user1Mapper.select("1");

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

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

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

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 - 請(qǐng)求被攔截,攔截類:[org.apache.ibatis.executor.resultset.DefaultResultSetHandler],請(qǐng)求方法:[handleResultSets]請(qǐng)求參數(shù)[[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 - 請(qǐng)求被攔截結(jié)果:[[User1{id='1', name='小明', user2=null}]]
[main] INFO com.sharkchili.mapper.MyBatisTest - 查詢結(jié)果:[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如何引入自定義插件?

我們的業(yè)務(wù)代碼如下,創(chuàng)建SqlSessionFactory:

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

步入build邏輯會(huì)看到xml解析相關(guān)代碼有一行,如下所示,可以看出他就是對(duì)xml文件中plugins標(biāo)簽進(jìn)行解析:

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

其內(nèi)部做的就是解析xml配置,生成攔截器對(duì)象MyInterceptor,并存放到interceptorChain中的一個(gè)名為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,邏輯非常簡(jiǎn)單,說(shuō)白了就是存到一個(gè)名為interceptors的list集合中,然后進(jìn)行鏈?zhǔn)秸{(diào)用:

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

執(zhí)行真正邏輯,調(diào)用插件:

//3)通過(guò)sqlsession執(zhí)行數(shù)據(jù)庫(kù)操作
        User1Mapper user1Mapper = sqlSession.getMapper(User1Mapper.class);
        User1 user = user1Mapper.select("1");

注意在Plugin的signatureMap插個(gè)斷點(diǎn),如下所示:

這時(shí)候進(jìn)行debug,我們可以看到堆棧中停在這樣一段代碼上。由于我們編寫(xiě)了一個(gè)結(jié)果解析的攔截插件MyInterceptor,所以在newResultSetHandler時(shí)會(huì)從上文注冊(cè)的interceptorChain中取出對(duì)應(yīng)處理器,給我們的resultSetHandler ,這過(guò)程中piugin類會(huì)通過(guò)Plugin.wrap(target, this);對(duì)我們的結(jié)果處理類進(jìn)行包裝。

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

所以我們步入看看wrap的邏輯,可以看到interceptor即我們自己編寫(xiě)的插件,他會(huì)通過(guò)getSignatureMap獲取這個(gè)我們編寫(xiě)插件MyInterceptor注解上的信息,通過(guò)反射生成一個(gè)新的代理對(duì)象,這個(gè)對(duì)象存放著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);
        // 使用插件包裝我們的目標(biāo)類
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

執(zhí)行SQL邏輯:

最終我們的結(jié)果處理插件會(huì)在handleResultSets階段發(fā)現(xiàn)signatureMap里面有值,當(dāng)前處理器有攔截,執(zhí)行this.interceptor.intercept(new Invocation(this.target, method, args))攔截相關(guān)處理器執(zhí)行我們的MyInterceptor邏輯。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
        //發(fā)現(xiàn)signatureMap有值
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            //methods 中包含我們的這個(gè)階段的方法handleResultSets,故調(diào)用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的分頁(yè)插件的實(shí)現(xiàn)原理

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

責(zé)任編輯:趙寧寧 來(lái)源: 寫(xiě)代碼的SharkChili
相關(guān)推薦

2010-02-03 16:15:05

Python語(yǔ)言

2010-01-15 10:22:24

C++語(yǔ)言

2011-07-07 09:12:46

智慧運(yùn)算WatsonPower

2023-10-27 13:59:30

Mybatis占位符

2010-02-04 11:06:14

2020-08-26 10:25:16

智慧

2010-12-01 14:37:00

2023-10-08 08:22:33

2021-03-18 08:18:15

ZooKeeper數(shù)據(jù)持久化

2022-03-18 15:55:15

鴻蒙操作系統(tǒng)架構(gòu)

2018-12-14 09:48:23

Redis數(shù)據(jù)故障

2021-07-26 05:33:59

自動(dòng)化領(lǐng)導(dǎo)CIO

2023-08-17 16:17:00

Docker前端

2017-09-21 08:16:33

數(shù)據(jù)存儲(chǔ)環(huán)境

2021-01-21 08:49:52

數(shù)據(jù)單體架構(gòu)

2017-08-17 09:46:42

大數(shù)據(jù)諸葛io數(shù)據(jù)挖掘

2011-07-10 16:04:01

程序員

2023-09-08 08:42:01

數(shù)據(jù)場(chǎng)景項(xiàng)目

2021-09-01 07:21:40

ArrayPool源碼Bucket
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)