亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么進行MyBatis源碼分析

發布時間:2021-11-16 16:48:27 來源:億速云 閱讀:151 作者:柒染 欄目:大數據

怎么進行MyBatis源碼分析,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

MyBatis源碼分析

  • What is MyBatis?

官方描述:
    MyBatis是一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。
    MyBatis免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。
    MyBatis可以通過簡單的XML或注解來配置和映射原始類型、接口和Java POJO(Plain Old Java Objects,普通老式Java對象)為數據庫中的記錄。
  • 首先來一個簡單的基礎的mybatis工程

pom.xml
<dependencies>
    <!-- mysql驅動 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <!-- mybatis依賴 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
</dependencies>
mybatis-config.xml
<?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>
    <properties resource="jdbc.properties" />

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.username}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>
UserMapper.xml
<?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="com.ch.mybatis.mapper.UserMapper">

    <select id="findById" resultType="com.ch.mybatis.pojo.User">
        select * from tb_user where id = #{id}
    </select>
</mapper>
UserMapper.java
public interface UserMapper {

    User findById(Integer id);
}
MyBatisTest.java
public static void main(String[] args) throws Exception {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = sqlSessionFactory.openSession();

    UserMapper userMapper = session.getMapper(UserMapper.class);
    User user = userMapper.findById(20);
}
  • 開始分析

// 點擊.getMapper 進去
UserMapper userMapper = session.getMapper(UserMapper.class);

-------------------------------------
MapperRegistry.class:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 分析這里
    // MapperProxyFactory: 看名字先猜測是一個 Mapper的代理工廠類
    // 
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        // 點這里進去 
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}


MapperProxyFactory.class:
public T newInstance(SqlSession sqlSession) {
    // MapperProxy: 點這個進去
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}


// 實現了 InvocationHandler 所以這個類是動態代理類
// 代理了誰?
// 前面的代碼: UserMapper userMapper = session.getMapper(UserMapper.class);
// 所以是代理了 UserMapper
// 問題? UserMapper是一個抽象類,而且沒有實現
// 看起來就是一個動態代理模式,但是jdk動態代理抽象接口必須要有實現類
// 所以這里什么情況?
public class MapperProxy<T> implements InvocationHandler, Serializable { }
  • JDK動態代理 和 這里的mybatis"動態代理?" 對比

## Jdk正宗版本:
IProxyInterface     InvocationHandler
      |                     |
ProxyImpl     <-    ProxyDemoProxy

---------------------

## Mybatis版本:
UserMapper          InvocationHandler
     |                    |
    ???       <-    MapperProxy
  • 接著分析

// 上面的流程我們知道 這里是通過動態代理拿到的一個 UserMapper 的實例
// 打斷點可以看到這里的 userMapper 的實例化內存地址是:
// org.apache.ibatis.binding.MapperProxy@3f197a46
UserMapper userMapper = session.getMapper(UserMapper.class);

// 接著怎么使用的呢?
// MapperProxy類實現了InvocationHandler,所以會執行里面的 invoke 方法
// 然后我們看看 invoke 里面做了什么 
// MapperProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 這里考慮一下  是走 if 還是走 else ?
        // 因為上面的分析得出 mybatis不是一個純正的動態代理
        // 所以這里一般會走 else  
        // 既然不會走這里為什么還有這行代碼? 不能因為不是它就不要它(有點繞口)
        if (Object.class.equals(method.getDeclaringClass())) {
            // 什么時候會進這里?
            // UserMapper userMapper = session.getMapper(UserMapper.class);
            // userMapper.toString(); 這種情況會進這里
            return method.invoke(this, args);
        } 
        else {
            // 接著我們看看 else 里面做了什么? 點 invoke 進去看 
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}


// 這個抽象類 有兩個實現類 PlainMethodInvoker 和 DefaultMethodInvoker
// 進哪個? 
// 看上面的 return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
// 發現最后的走到了 return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
// 所以看 PlainMethodInvoker
interface MapperMethodInvoker {
    // 抽象類,快捷鍵進入實現
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}


private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
        super();
        this.mapperMethod = mapperMethod;
    }

    // 直接看 invoke 方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        // 在看 execute 
        return mapperMethod.execute(sqlSession, args);
    }
}


// MapperMethod.class
// 上面我們調用的是 findById 是一個 select 語句,所以直接看 case SELECT:
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case SELECT:
            if (method.returnsVoid() && method.hasResultHandler()) {
                executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (method.returnsMany()) {
                result = executeForMany(sqlSession, args);
            } else if (method.returnsMap()) {
                result = executeForMap(sqlSession, args);
            } else if (method.returnsCursor()) {
                result = executeForCursor(sqlSession, args);
            } else {
                Object param = method.convertArgsToSqlCommandParam(args);

                // 斷點走到了這里,再看 selectOne 
                result = sqlSession.selectOne(command.getName(), param);
                if (method.returnsOptional()
                        && (result == null || !method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
    }
    return result;
}


// DefaultSqlSession.class
public <T> T selectOne(String statement, Object parameter) {

    // 看 selectList 里面 
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
         return list.get(0);
    } else if (list.size() > 1) {
        throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
        return null;
    }
}


@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
        MappedStatement ms = configuration.getMappedStatement(statement);
        // executor: 頂層接口,定義了執行器的一些基本操作
        // BaseExecutor:實現了Executor接口,實現了執行器的基本功能。
        // 具體使用哪一個Executor則是可以在 mybatis 的 config.xml 中進行配置的。默認為SimpleExecutor;
        // <settings>
        //     <setting name="defaultExecutorType" value="SIMPLE"/>  <!--SIMPLE、REUSE、BATCH-->
        // </settings>
        // 看 BaseExecutor
        return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}


// BaseExecutor.class
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 創建一級緩存的鍵對象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 調用下面的 query 方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
        throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
        clearLocalCache();
    }
    List<E> list;
    try {
        queryStack++;
        // 先在緩存中查詢,緩存命中失敗再去數據庫查詢
        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
        if (list != null) {
            handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
        } else {
            // 一級緩存中沒有,走這里
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    } finally {
        queryStack--;
    }
    if (queryStack == 0) {
        for (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
        }
        // issue #601
        deferredLoads.clear();
        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
        }
    }
    return list;
}


private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
        // 到了這里,點下去看
        // 點進去是個抽象的,看實現,上面提到的默認使用的 SimpleExecutor
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
        localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
    }
    return list;
}


// SimpleExecutor.class
// 注意這里面的邏輯 
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
        Configuration configuration = ms.getConfiguration();

        // RoutingStatementHandler.delegate = PreparedStatementHandler
        StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        
        // com.mysql.jdbc.JDBC42PreparedStatement@757acd7b: select * from tb_user where id = 20
        stmt = prepareStatement(handler, ms.getStatementLog());

        // handler = PreparedStatementHandler
        // 點進去繼續看 
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}


// PreparedStatementHandler.class
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 這幾行代碼是不是很熟悉?
    // 就是jdbc的執行代碼 
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    
    // resultSetHandler 結果集處理器 
    return resultSetHandler.handleResultSets(ps);
}
  • 總結

1.分析得出mybatis的最底層還是封裝的jdbc代碼。

2.看起來很像動態代理,
  其實是根據 UserMapper的抽象方法名去對應的在mybatis的xml配置文件中查找對應的id(<select id="xxx">)而獲得到sql語句
  (為什么UserMapper.xml中的 <mapper> 標簽中的 namespace 屬性的作用就在這里)
  到到了sql語句,最底層用jdbc代碼執行sql。

3.下面這兩行代碼在build的時候就把xml里面的sql語句解析出來出來放在
    DefaultSqlSessionFactory(SqlSessionFactory)中了 (build最后返回了 new DefaultSqlSessionFactory(config))

  InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

關于怎么進行MyBatis源碼分析問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

慈利县| 建昌县| 宁明县| 车险| 临沂市| 宁远县| 乌兰浩特市| 满洲里市| 克什克腾旗| 兴隆县| 洱源县| 孝感市| 长海县| 江孜县| 鹤峰县| 望都县| 金乡县| 龙海市| 个旧市| 阜宁县| 石狮市| 禹州市| 高台县| 孝义市| 左权县| 合作市| 康保县| 盖州市| 丰城市| 望奎县| 克东县| 迁安市| 五家渠市| 恭城| 石渠县| 桑日县| 东宁县| 巴彦淖尔市| 鞍山市| 都江堰市| 广灵县|