- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- 淺談MyBatis 如何執行一條 SQL語(yǔ)句
Mybatis 是 Java 開(kāi)發(fā)中比較常用的 ORM 框架。在日常工作中,我們都是直接通過(guò) Spring Boot 自動(dòng)配置,并直接使用,但是卻不知道 Mybatis 是如何執行一條 SQL 語(yǔ)句的,而這篇文章就是來(lái)揭開(kāi) Mybatis 的神秘面紗。
我們要理解 Mybatis 的執行過(guò)程,就必須先了解 Mybatis 中都有哪一些重要的類(lèi),這些類(lèi)的職責都是什么?
SqlSession
我們都很熟悉,它對外提供用戶(hù)和數據庫之間交互需要使用的方法,隱藏了底層的細節。它默認是實(shí)現類(lèi)是 DefaultSqlSession
Executor
這個(gè)是執行器,SqlSession 中對數據庫的操作都是委托給它。它有多個(gè)實(shí)現類(lèi),可以使用不同的功能。
Configuration
它是一個(gè)很重要的配置類(lèi),它包含了 Mybatis 的所有有用信息,包括 xml 配置,動(dòng)態(tài) sql 語(yǔ)句等等,我們到處都可以看到這個(gè)類(lèi)的身影。
MapperProxy
這是一個(gè)很重要的代理類(lèi),它代理的就是 Mybatis 中映射 SQL 的接口。也就是我們常寫(xiě)的 Dao 接口。
首先,我們需要得到一個(gè) SqlSessionFactory 對象,該對象的作用是可以獲取 SqlSession 對象。
// 讀取配置 InputStream resourceAsStream = Resources.getResourceAsStream("config.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // 創(chuàng )建一個(gè) SqlSessionFactory 對象 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
當我們得到一個(gè) SqlSessionFactory 對象之后,就可以通過(guò)它的 openSession 方法得到一個(gè) SqlSession 對象。
SqlSession sqlSession = sqlSessionFactory.openSession(true);
最后,我們通過(guò) SqlSession 對象獲取 Mapper ,從而可以從數據庫獲取數據。
// 獲取 Mapper 對象 HeroMapper mapper = sqlSession.getMapper(HeroMapper.class); // 執行方法,從數據庫中獲取數據 Hero hero = mapper.selectById(1);
我們現在主要關(guān)注的就是 getMapper 方法,該方法為我們創(chuàng )建一個(gè)代理對象,該代理對象為我們執行 SQL 語(yǔ)句提供了重要的支持。
// SqlSession 對象 @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); }
getMapper 方法里面委托 Configuration 對象去獲取對應的 Mapper 代理對象,之前說(shuō)過(guò) Configuration 對象里面包含了 Mybatis 中所有重要的信息,其中就包括我們需要的 Mapper 代理對象,而這些信息都是在讀取配置信息的時(shí)候完成的,也就是執行sqlSessionFactoryBuilder.build 方法。
// Configuration 對象 public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
我們可以看到它又將獲取 Mapper 代理對象的操作委托給了 MapperRegistry 對象(擱著(zhù)俄羅斯套娃呢?),這個(gè) MapperRegistry 對象里面就存放了我們想要的 Mapper 代理對象,如果你這么想,就錯了,實(shí)際上,它存放的并不是我們想要的 Mapper 代理對象,而是 Mapper 代理對象的工廠(chǎng),Mybatis 這里使用到了工廠(chǎng)模式。
public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); public MapperRegistry(Configuration config) { this.config = config; } @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { 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); } } public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } }
我只保留了 getMapper 方法和 addMapper 方法。
在 getMapper 方法中,它獲取的是 MapperProxyFactory 對象,我們通過(guò)名稱(chēng)可以得出這是一個(gè) Mapper 代理對象工廠(chǎng),但是我們是要得到一個(gè) MapperProxy 對象,而不是一個(gè)工廠(chǎng)對象,我們再來(lái)看 getMapper 方法,它通過(guò) mapperProxyFactory.newInstance 來(lái)創(chuàng )建代理對象。
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
創(chuàng )建了一個(gè) MapperProxy 對象,并且通過(guò) Proxy.newProxyInstance 方法(不會(huì )還有人不知道這是 JDK 動(dòng)態(tài)代理吧),創(chuàng )建一個(gè)代理對象處理,這個(gè)代理對象就是我們想要的結果。這里沒(méi)有體現出來(lái)代理了哪個(gè)對象???其實(shí) mapperInterface 這是一個(gè)成員變量,它引用了需要被代理的對象。而這個(gè)成員變量實(shí)在創(chuàng )建 MapperProxyFactory 對象的時(shí)候賦值的,所以我們每一個(gè)需要被代理的接口,在 Mybatis 中都會(huì )為它生成一個(gè) MapperProxyFactory 對象,該對象的作用就是為了創(chuàng )建所需要的代理對象。
當我們獲取到代理對象 mapper 之后,就可以執行它里面的方法。
這里使用一個(gè)例子:
// Myabtis 所需要的接口 public interface HeroMapper { Hero selectById(Integer id); }
// HeroMapper 接口所對應的 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="test.HeroMapper"> <select id="selectById" resultType="test.Hero"> select * from hero where id = #{id} </select> </mapper>
我們執行 selectById 方法,獲取一個(gè)用戶(hù)的信息。
// 獲取 Mapper 對象 HeroMapper mapper = sqlSession.getMapper(HeroMapper.class); // 執行方法,從數據庫中獲取數據 Hero hero = mapper.selectById(1);
通過(guò)上面的解析已經(jīng)知道,這里的 mapper 是一個(gè)代理對象的引用,而這個(gè)代理類(lèi)則是 MapperProxy,所以我們主要是去了解 MapperProxy 這個(gè)代理類(lèi)做了什么事情。
public class MapperProxy<T> implements InvocationHandler, Serializable { private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { return methodCache.computeIfAbsent(method, m -> { return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } } private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { super(); this.mapperMethod = mapperMethod; } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } } }
代理對象執行方法時(shí)都是直接執行 invoke() 方法,在這個(gè)方法中,我們主要就看一條語(yǔ)句 cachedInvoker(method).invoke(proxy, method, args, sqlSession);
我們首先看 cachedInvoker 方法,它的參數是 Method 類(lèi)型,所以這個(gè) method 表示的就是我們執行的方法 HeroMapper.selectById,它首先從緩存中獲取是否之前已經(jīng)創(chuàng )建過(guò)一個(gè)該方法的方法執行器 PlainMethodInvoker 對象,其實(shí)這只是一個(gè)包裝類(lèi),可有可無(wú),在工程上來(lái)說(shuō),有了這個(gè)包裝類(lèi),會(huì )更加易于維護。而這個(gè)執行器里面只有一個(gè)成員對象,這個(gè)成員對象就是 MapperMethod,并且這個(gè) MapperMethod 的構造函數中需要傳遞 HeroMapper、HeroMapper.selectById、Cofiguration 這三個(gè)參數。
以上步驟都執行完成之后,接下來(lái)我們可以看到執行了 PlainMethodInvoker 的 invoke 方法,而它又將真正的操作委托給了 MapperMethod,執行 MapperMethod 下的 execute 方法,這個(gè)方法就是本文章的重點(diǎn)所在。
從上面的解析可以知道,最后會(huì )執行到這個(gè)方法之中。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } 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); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } return result; }
這個(gè)方法中,我們可以看到熟悉的幾個(gè)關(guān)鍵字:select、update、delete、insert,這個(gè)就是為了找到執行方式,我們因為是 select 語(yǔ)句,所以分支會(huì )走向 select,并且最終會(huì )執行到 sqlSession.selectOne 方法中,所以最終饒了一大圈,依然還是回到了我們一開(kāi)始就提到的 SqlSession 對象中。
在這個(gè)方法中,首先會(huì )構造參數,也就是我們看到的 convertArgsToSqlCommandParam 方法,它的內部執行方式是按照如下方式來(lái)轉換參數的:
使用 @param 自定義命名
amethod(@Param int a, @Param int b) 則會(huì )構造 map -> [{"a", a_arg}, {"b", b_arg}, {"param1", a_arg}, {"param2", b_arg}],a 和 param1 是對參數 a 的命名,a_arg 是傳遞的實(shí)際的值。
雖然只有兩個(gè)參數,但是最后卻會(huì )在 Map 存在四個(gè)鍵值對,因為 Mybatis 最后自己會(huì )生成以 param 為前綴的參數名稱(chēng),名稱(chēng)按照參數的位置進(jìn)行命名。
不使用 @param
amethod(int a, int b),則會(huì )構造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}],因為沒(méi)有對參數進(jìn)行自定義命名,所以 Myabtis 就對參數取了一個(gè)默認的名稱(chēng),以 arg 為前綴,位置為后綴進(jìn)行命名。
在參數只有一個(gè),并且參數為集合的情況下,會(huì )存放多個(gè)鍵值對:
不會(huì )作為參數的對象
在 Mybatis 中有兩個(gè)特殊的對象:RowBounds、ResultHandler,這兩個(gè)對象如果作為參數則不會(huì )放入到 map 中,但是會(huì )占據位置。
amethod(int a,RowBounds rb, int b),這種情況下,會(huì )構造 map -> [{"arg0", a_arg}, {"arg2", b_arg}, {"param1", a_arg}, {"param2", b_arg}]
注意這里的 b 參數的命名分別是 arg2 和 param2,arg2 是因為它的位置在參數的第 3 位,而 param2 則是因為它是第 2 個(gè)有效參數。
參數構造完成之后,我們就需要尋找需要執行的 SQL 語(yǔ)句了。
@Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. 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; } }
這里的 statement 雖然是 String 類(lèi)型的,但是它并不是真正的 SQL 語(yǔ)句,它是一個(gè)尋找對應 MapperStatement 對象的名稱(chēng),在我們的例子中,它就是 test.HeroMapper.selectById ,Mybatis 通過(guò)這個(gè)名稱(chēng)可以尋找到包含了 SQL 語(yǔ)句的對象。
我們跟蹤代碼的執行,最后會(huì )來(lái)到下面這個(gè)方法,這是一個(gè)包含三個(gè)參數的重載方法。
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); 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(); } }
在第四行代碼中,可以得知它通過(guò) statement 從 Configuration 對象中獲取了一個(gè) MapperStatement 對象, MapperStatement 對象包含的信息是由 <select>、<update>、<delete> 、<insert> 元素提供的,我們在這些元素中定義的信息都會(huì )保存在該對象中,如:Sql 語(yǔ)句、resultMap、fetchSize 等等。
獲取到包含 SQL 語(yǔ)句信息的對象之后,就會(huì )交給 Execute 執行器對象去執行后續的處理,也就是 executor.query 方法。
@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); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
獲取需要自行的 Sql 語(yǔ)句,然后創(chuàng )建一個(gè)緩存使用的 key,用于二級緩存。
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // .... // 跟緩存有關(guān),如果緩存中存在數據,則直接從緩存中返回,否則從數據庫中查詢(xún) list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); return list; }
最后會(huì )執行到一個(gè) doQuery 方法
@Override 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(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
這段代碼創(chuàng )建了一個(gè) Statement 對象的處理器 StatementHandler,這個(gè)處理器主要的工作就是完成 JDBC 中 PrepareStatement 對象的一些準備工作,包括:創(chuàng )建 PrepareStatement 對象,設置需要執行的 sql 語(yǔ)句,為 sql 語(yǔ)句中的參數賦值。完成這些工作之后,就開(kāi)始從數據庫獲取數據了。
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }
第四行代碼即執行對應的 Sql 查詢(xún),后續則是對結果進(jìn)行處理。
Mybatis 通過(guò) MapperProxy 代理了我們的 Dao 接口類(lèi),以此來(lái)幫助我們執行預定義的 Sql 語(yǔ)句,通過(guò) Cache 來(lái)緩存對應的執行結果,通過(guò) StatementHandler 創(chuàng )建 PrepareStatement 對象,通過(guò) jdbc 執行 SQL 操作。
到此這篇關(guān)于淺談MyBatis 如何執行一條 SQL語(yǔ)句的文章就介紹到這了,更多相關(guān)MyBatis 執行SQL語(yǔ)句內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站