国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術文章
文章詳情頁

淺談MyBatis 如何執(zhí)行一條 SQL語句

瀏覽:31日期:2023-10-19 11:51:54
目錄前言基礎組件工作流程初步使用詳細流程獲取 MapperProxy 對象緩存執(zhí)行方法構造參數(shù)獲取需要執(zhí)行的 SQL 對象執(zhí)行 SQL 語句總結前言

Mybatis 是 Java 開發(fā)中比較常用的 ORM 框架。在日常工作中,我們都是直接通過 Spring Boot 自動配置,并直接使用,但是卻不知道 Mybatis 是如何執(zhí)行一條 SQL 語句的,而這篇文章就是來揭開 Mybatis 的神秘面紗。

基礎組件

我們要理解 Mybatis 的執(zhí)行過程,就必須先了解 Mybatis 中都有哪一些重要的類,這些類的職責都是什么?

SqlSession

我們都很熟悉,它對外提供用戶和數(shù)據(jù)庫之間交互需要使用的方法,隱藏了底層的細節(jié)。它默認是實現(xiàn)類是 DefaultSqlSession

Executor

這個是執(zhí)行器,SqlSession 中對數(shù)據(jù)庫的操作都是委托給它。它有多個實現(xiàn)類,可以使用不同的功能。

淺談MyBatis 如何執(zhí)行一條 SQL語句

Configuration

它是一個很重要的配置類,它包含了 Mybatis 的所有有用信息,包括 xml 配置,動態(tài) sql 語句等等,我們到處都可以看到這個類的身影。

MapperProxy

這是一個很重要的代理類,它代理的就是 Mybatis 中映射 SQL 的接口。也就是我們常寫的 Dao 接口。

工作流程初步使用

首先,我們需要得到一個 SqlSessionFactory 對象,該對象的作用是可以獲取 SqlSession 對象。

// 讀取配置InputStream resourceAsStream = Resources.getResourceAsStream('config.xml');SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 創(chuàng)建一個 SqlSessionFactory 對象SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

當我們得到一個 SqlSessionFactory 對象之后,就可以通過它的 openSession 方法得到一個 SqlSession 對象。

SqlSession sqlSession = sqlSessionFactory.openSession(true);

最后,我們通過 SqlSession 對象獲取 Mapper ,從而可以從數(shù)據(jù)庫獲取數(shù)據(jù)。

// 獲取 Mapper 對象HeroMapper mapper = sqlSession.getMapper(HeroMapper.class);// 執(zhí)行方法,從數(shù)據(jù)庫中獲取數(shù)據(jù)Hero hero = mapper.selectById(1);詳細流程獲取 MapperProxy 對象

我們現(xiàn)在主要關注的就是 getMapper 方法,該方法為我們創(chuàng)建一個代理對象,該代理對象為我們執(zhí)行 SQL 語句提供了重要的支持。

// SqlSession 對象@Overridepublic <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this);}

getMapper 方法里面委托 Configuration 對象去獲取對應的 Mapper 代理對象,之前說過 Configuration 對象里面包含了 Mybatis 中所有重要的信息,其中就包括我們需要的 Mapper 代理對象,而這些信息都是在讀取配置信息的時候完成的,也就是執(zhí)行sqlSessionFactoryBuilder.build 方法。

// Configuration 對象public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession);}

我們可以看到它又將獲取 Mapper 代理對象的操作委托給了 MapperRegistry 對象(擱著俄羅斯套娃呢?),這個 MapperRegistry 對象里面就存放了我們想要的 Mapper 代理對象,如果你這么想,就錯了,實際上,它存放的并不是我們想要的 Mapper 代理對象,而是 Mapper 代理對象的工廠,Mybatis 這里使用到了工廠模式。

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 對象,我們通過名稱可以得出這是一個 Mapper 代理對象工廠,但是我們是要得到一個 MapperProxy 對象,而不是一個工廠對象,我們再來看 getMapper 方法,它通過 mapperProxyFactory.newInstance 來創(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)建了一個 MapperProxy 對象,并且通過 Proxy.newProxyInstance 方法(不會還有人不知道這是 JDK 動態(tài)代理吧),創(chuàng)建一個代理對象處理,這個代理對象就是我們想要的結果。這里沒有體現(xiàn)出來代理了哪個對象啊?其實 mapperInterface 這是一個成員變量,它引用了需要被代理的對象。而這個成員變量實在創(chuàng)建 MapperProxyFactory 對象的時候賦值的,所以我們每一個需要被代理的接口,在 Mybatis 中都會為它生成一個 MapperProxyFactory 對象,該對象的作用就是為了創(chuàng)建所需要的代理對象。

淺談MyBatis 如何執(zhí)行一條 SQL語句

緩存執(zhí)行方法

當我們獲取到代理對象 mapper 之后,就可以執(zhí)行它里面的方法。這里使用一個例子:

// 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 resultType='test.Hero'>select * from hero where id = #{id} </select></mapper>

我們執(zhí)行 selectById 方法,獲取一個用戶的信息。

// 獲取 Mapper 對象HeroMapper mapper = sqlSession.getMapper(HeroMapper.class);// 執(zhí)行方法,從數(shù)據(jù)庫中獲取數(shù)據(jù)Hero hero = mapper.selectById(1);

通過上面的解析已經(jīng)知道,這里的 mapper 是一個代理對象的引用,而這個代理類則是 MapperProxy,所以我們主要是去了解 MapperProxy 這個代理類做了什么事情。

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); } }}

代理對象執(zhí)行方法時都是直接執(zhí)行 invoke() 方法,在這個方法中,我們主要就看一條語句 cachedInvoker(method).invoke(proxy, method, args, sqlSession);

我們首先看 cachedInvoker 方法,它的參數(shù)是 Method 類型,所以這個 method 表示的就是我們執(zhí)行的方法 HeroMapper.selectById,它首先從緩存中獲取是否之前已經(jīng)創(chuàng)建過一個該方法的方法執(zhí)行器 PlainMethodInvoker 對象,其實這只是一個包裝類,可有可無,在工程上來說,有了這個包裝類,會更加易于維護。而這個執(zhí)行器里面只有一個成員對象,這個成員對象就是 MapperMethod,并且這個 MapperMethod 的構造函數(shù)中需要傳遞 HeroMapper、HeroMapper.selectById、Cofiguration 這三個參數(shù)。

以上步驟都執(zhí)行完成之后,接下來我們可以看到執(zhí)行了 PlainMethodInvoker 的 invoke 方法,而它又將真正的操作委托給了 MapperMethod,執(zhí)行 MapperMethod 下的 execute 方法,這個方法就是本文章的重點所在。

淺談MyBatis 如何執(zhí)行一條 SQL語句

構造參數(shù)

從上面的解析可以知道,最后會執(zhí)行到這個方法之中。

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; }

這個方法中,我們可以看到熟悉的幾個關鍵字:select、update、delete、insert,這個就是為了找到執(zhí)行方式,我們因為是 select 語句,所以分支會走向 select,并且最終會執(zhí)行到 sqlSession.selectOne 方法中,所以最終饒了一大圈,依然還是回到了我們一開始就提到的 SqlSession 對象中。在這個方法中,首先會構造參數(shù),也就是我們看到的 convertArgsToSqlCommandParam 方法,它的內(nèi)部執(zhí)行方式是按照如下方式來轉換參數(shù)的:

使用 @param 自定義命名amethod(@Param int a, @Param int b) 則會構造 map -> [{'a', a_arg}, {'b', b_arg}, {'param1', a_arg}, {'param2', b_arg}],a 和 param1 是對參數(shù) a 的命名,a_arg 是傳遞的實際的值。雖然只有兩個參數(shù),但是最后卻會在 Map 存在四個鍵值對,因為 Mybatis 最后自己會生成以 param 為前綴的參數(shù)名稱,名稱按照參數(shù)的位置進行命名。

不使用 @param

amethod(int a, int b),則會構造 map -> [{'arg0', a_arg}, {'arg1', b_arg}, {'param1', a_arg}, {'param2', b_arg}],因為沒有對參數(shù)進行自定義命名,所以 Myabtis 就對參數(shù)取了一個默認的名稱,以 arg 為前綴,位置為后綴進行命名。

在參數(shù)只有一個,并且參數(shù)為集合的情況下,會存放多個鍵值對:

amethod(Collection<Integer> a),這種情況下,會構造 map -> [{'arg0', a_arg}, {'collection', a_arg}] amethod(List<Integer> a),這種情況下,會構造 map -> [{'arg0', a_arg}, {'collection', a_arg}, {'list', a_arg}] amethod(Integer[] a),這種情況下,會構造 map -> [{'arg0', a_arg}, {'array', a_arg}] 但是,如果有兩個參數(shù),那么就不會這么存放,而是按照常規(guī)的方式: amethod(List<Integer> a,List<Integer> b) 則會構造 map -> [{'arg0', a_arg}, {'arg1', b_arg}, {'param1', a_arg}, {'param2', b_arg}] amethod(List<Integer> a,int b) 則會構造 map -> [{'arg0', a_arg}, {'arg1', b_arg}, {'param1', a_arg}, {'param2', b_arg}]

不會作為參數(shù)的對象在 Mybatis 中有兩個特殊的對象:RowBounds、ResultHandler,這兩個對象如果作為參數(shù)則不會放入到 map 中,但是會占據(jù)位置。

amethod(int a,RowBounds rb, int b),這種情況下,會構造 map -> [{'arg0', a_arg}, {'arg2', b_arg}, {'param1', a_arg}, {'param2', b_arg}]

注意這里的 b 參數(shù)的命名分別是 arg2 和 param2,arg2 是因為它的位置在參數(shù)的第 3 位,而 param2 則是因為它是第 2 個有效參數(shù)。

獲取需要執(zhí)行的 SQL 對象

參數(shù)構造完成之后,我們就需要尋找需要執(zhí)行的 SQL 語句了。

@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 類型的,但是它并不是真正的 SQL 語句,它是一個尋找對應 MapperStatement 對象的名稱,在我們的例子中,它就是 test.HeroMapper.selectById ,Mybatis 通過這個名稱可以尋找到包含了 SQL 語句的對象。

我們跟蹤代碼的執(zhí)行,最后會來到下面這個方法,這是一個包含三個參數(shù)的重載方法。

@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(); } }

在第四行代碼中,可以得知它通過 statement 從 Configuration 對象中獲取了一個 MapperStatement 對象, MapperStatement 對象包含的信息是由 <select>、<update>、<delete> 、<insert> 元素提供的,我們在這些元素中定義的信息都會保存在該對象中,如:Sql 語句、resultMap、fetchSize 等等。

執(zhí)行 SQL 語句

獲取到包含 SQL 語句信息的對象之后,就會交給 Execute 執(zhí)行器對象去執(zhí)行后續(xù)的處理,也就是 executor.query 方法。

@Overridepublic <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 語句,然后創(chuàng)建一個緩存使用的 key,用于二級緩存。

@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // .... // 跟緩存有關,如果緩存中存在數(shù)據(jù),則直接從緩存中返回,否則從數(shù)據(jù)庫中查詢 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); return list;}

最后會執(zhí)行到一個 doQuery 方法

@Overridepublic <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)建了一個 Statement 對象的處理器 StatementHandler,這個處理器主要的工作就是完成 JDBC 中 PrepareStatement 對象的一些準備工作,包括:創(chuàng)建 PrepareStatement 對象,設置需要執(zhí)行的 sql 語句,為 sql 語句中的參數(shù)賦值。完成這些工作之后,就開始從數(shù)據(jù)庫獲取數(shù)據(jù)了。

@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps);}

第四行代碼即執(zhí)行對應的 Sql 查詢,后續(xù)則是對結果進行處理。

總結

Mybatis 通過 MapperProxy 代理了我們的 Dao 接口類,以此來幫助我們執(zhí)行預定義的 Sql 語句,通過 Cache 來緩存對應的執(zhí)行結果,通過 StatementHandler 創(chuàng)建 PrepareStatement 對象,通過 jdbc 執(zhí)行 SQL 操作。

到此這篇關于淺談MyBatis 如何執(zhí)行一條 SQL語句的文章就介紹到這了,更多相關MyBatis 執(zhí)行SQL語句內(nèi)容請搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持好吧啦網(wǎng)!

相關文章:
主站蜘蛛池模板: 久草观看视频 | xxxxfreexxxx人妖 | 欧美视频一区二区三区在线观看 | 国产在线成人一区二区 | 国产a一级毛片含羞草传媒 国产a自拍 | 免费精品久久久视频 | 亚洲资源在线观看 | 国产成人精品免费视 | 精品一区二区三区三区 | 99热免费在线 | 国产r67194吃奶视频 | 国产精品yjizz视频网一二区 | 欧美色视频在线观看 | 亚洲一级毛片免费看 | 成人在线视频一区 | 亚洲一区二区三区国产精品 | 九九在线精品 | 午夜三级a三级三点在线观看 | 香蕉99国内自产自拍视频 | se就是色94欧美setu | 欧美成人一级毛片 | 18videosex性欧美69超高清 | 欧美一级欧美一级毛片 | 日韩在线视频网址 | 亚洲天堂日韩在线 | 成人欧美日韩高清不卡 | 中文字幕在线观看91 | 欧美kkk4444在线观看 | 中文字幕乱码系列免费 | 国内精品一区二区三区最新 | 成人精品免费视频 | 1级a的观看视频 | 三级毛片免费观看 | 欧美日韩另类国产 | 午夜两性试爱视频免费 | 亚洲成a人不卡在线观看 | 国产精品国产亚洲精品不卡 | 久久精品网站免费观看 | 国产精品日本不卡一区二区 | 国产精品美女一区二区 | 中文字幕一区二区三区 精品 |