您好,登錄后才能下訂單哦!
怎么進行MyBatis源碼分析,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
What is MyBatis?
官方描述: MyBatis是一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。 MyBatis免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。 MyBatis可以通過簡單的XML或注解來配置和映射原始類型、接口和Java POJO(Plain Old Java Objects,普通老式Java對象)為數據庫中的記錄。
首先來一個簡單的基礎的mybatis工程
<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>
<?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>
<?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>
public interface UserMapper { User findById(Integer id); }
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源碼分析問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。