摘要:避免了幾乎所有的代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。這個對象主要是獲取方法對應(yīng)的命令和執(zhí)行相應(yīng)操作等的處理,具體細(xì)節(jié)同學(xué)們可以抽空研究。所以這里的方法主要使用了和對象幫助我們處理語句集和參數(shù)的處理。
博文目標(biāo):希望大家看了這篇博文后,對Mybatis整體運行過程有一個清晰的認(rèn)識和把握。 1.什么是 MyBatis ?
MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄。(這是官網(wǎng)解釋)
2.MyBatis運行原理 框架圖解釋說明當(dāng)框架啟動時,通過configuration解析config.xml配置文件和mapper.xml映射文件,映射文件可以使用xml方式或者注解方式,然后由configuration獲得sqlsessionfactory對象,再由sqlsessionfactory獲得sqlsession數(shù)據(jù)庫訪問會話對象,通過會話對象獲得對應(yīng)DAO層的mapper對象,通過調(diào)用mapper對象相應(yīng)方法,框架就會自動執(zhí)行SQL語句從而獲得結(jié)果。
講完了,6不6,可以,牛逼,就這么簡單。此時心中是否有千萬只草泥馬奔涌而出,別急,對于上述,我會在下面針對重點進(jìn)行一一講解。
這里請大家自行百度解決,網(wǎng)上也有比較多的解析庫,對于大家來說應(yīng)該是沒有什么問題,我們這邊主要抓住框架運行的總體過程。對于細(xì)節(jié)大家可以課后慢慢研究。
mybatis啟動(編程式)
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
我們再來看下這個build操作在底層做了什么
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException var13) { ; } } return var5; } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
我們可以很明顯的看到mybatis通過XMLConfigBuilder初始化并且解析了我們的配置文件,最后得到一個Configuration類型的對象傳給另外一個build操作,這個build操作最后直接new了一個DefaultSqlSessionFactory對象并且返回。
4.何為Mapper對象?通過上面的敘述我們已經(jīng)知道我們與mybatis交互主要是通過配置文件或者配置對象,但是我們最終的目的是要操作數(shù)據(jù)庫的,所以mybatis為我們提供了sqlSession這個對象來進(jìn)行所有的操作,也就是說我們真正通過mybatis操作數(shù)據(jù)庫只要對接sqlSession這個對象就可以了。那么問題來了,我們怎么樣通過sqlSession來了操作數(shù)據(jù)庫的呢?
問題1:如何獲取sqlSession?
public SqlSession openSession() { return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; DefaultSqlSession var8; try { Environment environment = this.configuration.getEnvironment(); TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); Executor executor = this.configuration.newExecutor(tx, execType); var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8; }
由上面代碼我們可知我們可以通過SqlSessionFactory的openSession去獲取我們的sqlSession,也就是默認(rèn)得到一個DefaultSqlSession對象。
問題2:Mapper對象怎么來的?
平時我們使用如下代碼獲得一個Mapper對象。
publicT getMapper(Class type) { return this.configuration.getMapper(type, this); }
通過調(diào)用DefaultSqlSession的getMapper方法并且傳入一個類型對象獲取,底層呢調(diào)用的是配置對象configuration的getMapper方法,configuration對象是我們在加載DefaultSqlSessionFactory時傳入的。
然后我們再來看下這個配置對象的getMapper,傳入的是類型對象(補(bǔ)充一點這個類型對象就是我們平時寫的DAO層接口,里面是一些數(shù)據(jù)庫操作的接口方法。),和自身也就是DefaultSqlSession。
publicT getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }
我們看到這個configuration的getMapper方法里調(diào)用的是mapperRegistry的getMapper方法,參數(shù)依然是類型對象和sqlSession。這里呢,我們要先來看下這個MapperRegistry即所謂Mapper注冊器是什么。
public class MapperRegistry { private final Configuration config; private final Map, MapperProxyFactory>> knownMappers = new HashMap(); public MapperRegistry(Configuration config) { this.config = config; } .... }
從這里我們可以知道其實啊這個MapperRegistry就是保持了一個Configuration對象和一個HashMap,而這個HashMap的key是類型對象,value呢是MapperProxyFactory。我們這里先不管MapperProxyFactory是什么東西,我們現(xiàn)在只需要知道MapperRegistry是這么一個東西就可以了。這里有人會問MapperRegistry對象是怎么來的,這里呢是在初始化Configuration對象時初始化了這個MapperRegistry對象的,代碼大家可以去看,為了避免混亂,保持貼出來的代碼是一條線走下來的,這里就不貼出來了。接下來我們繼續(xù)看下這個MapperRegistry的getMapper方法。
publicT getMapper(Class type, SqlSession sqlSession) { MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
這里我們可以看到從knownMappers中獲取key為類型對象的MapperProxyFactory對象。然后調(diào)用MapperProxyFactory對象的newInstance方法返回,newInstance方法傳入sqlSession對象。到這里我們可能看不出什么端倪,那我們就繼續(xù)往下看這個newInstance方法做的什么事情吧。
public class MapperProxyFactory{ private final Class mapperInterface; private final Map methodCache = new ConcurrentHashMap(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; } public Class getMapperInterface() { return this.mapperInterface; } public Map getMethodCache() { return this.methodCache; } protected T newInstance(MapperProxy mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); } public T newInstance(SqlSession sqlSession) { MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } }
這里我們可以看到MapperProxyFactory直接new了一個MapperProxy對象,然后調(diào)用另外一重載的newInstance方法傳入MapperProxy對象。這里我們可以看出一些東西了,通過調(diào)用Proxy.newProxyInstance動態(tài)代理了我們的mapperProxy對象!這里的mapperInterface即我們的dao層(持久層)接口的類型對象。
所以總結(jié)下就是我們通過sqlSesssion.getMapper(clazz)得到的Mapper對象是一個mapperProxy的代理類!
所以也就引出下面的問題。
問題3:為什么我調(diào)用mapper對象方法就能發(fā)出sql操作數(shù)據(jù)庫?
通過上面的講解,我們知道了這個mapper對象其實是一個一個mapperProxy的代理類!所以呢這個mapperProxy必然實現(xiàn)了InvocationHandler接口。
public class MapperProxyimplements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } .... }
所以當(dāng)我們調(diào)用我們的持久層接口的方法時必然就會調(diào)用到這個MapperProxy對象的invoke方法,所以接下來我們進(jìn)入這個方法看看具體mybatis為我們做了什么。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); } } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); } return mapperMethod; }
從代碼中我們可以看到前面做了一個判斷,這個判斷主要是防止我們調(diào)用像toString方法或者equals方法時也能正常調(diào)用。然后我們可以看到它調(diào)用cachedMapperMethod返回MapperMethod對象,接著就執(zhí)行這個MapperMethod對象的execute方法。這個cachedMapperMethod方法主要是能緩存我們使用過的一些mapperMethod對象,方便下次使用。這個MapperMethod對象主要是獲取方法對應(yīng)的sql命令和執(zhí)行相應(yīng)SQL操作等的處理,具體細(xì)節(jié)同學(xué)們可以抽空研究。
public class MapperMethod { private final MapperMethod.SqlCommand command; private final MapperMethod.MethodSignature method; public MapperMethod(Class> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, mapperInterface, method); } .... }
說到這個mapperMethod對象的execute方法,我們看下代碼具體做了什么事情吧。
public Object execute(SqlSession sqlSession, Object[] args) { Object param; Object result; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method "" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
我們可以清晰的看到這里針對數(shù)據(jù)庫的增刪改查做了對應(yīng)的操作,這里我們可以看下查詢操作。我們可以看到這里針對方法的不同返回值作了不同的處理,我們看下其中一種情況。
param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param);
這里我們可以看到它將方法參數(shù)類型轉(zhuǎn)換成數(shù)據(jù)庫層面上的參數(shù)類型,最后調(diào)用sqlSession對象的selectOne方法執(zhí)行。所以我們看到最后還是回到sqlSession對象上來,也就是前面所說的sqlSession是mybatis提供的與數(shù)據(jù)庫交互的唯一對象。
接下來我們看下這個selectOne方法做了什么事,這里我們看的是defaultSqlSession的selectOne方法。
publicT selectOne(String statement, Object parameter) { List 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; } }
我們看到它調(diào)用selectList方法,通過去返回值的第一個值作為結(jié)果返回。那么我們來看下這個selectList方法。
publicList selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } public List selectList(String statement, Object parameter, RowBounds rowBounds) { List var5; try { MappedStatement ms = this.configuration.getMappedStatement(statement); var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception var9) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9); } finally { ErrorContext.instance().reset(); } return var5; }
我們可以看到這里調(diào)用了executor的query方法,我們再進(jìn)入到query里看看。這里我們看的是BaseExecutor的query方法。
publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql); return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql); } public List 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 (this.closed) { throw new ExecutorException("Executor was closed."); } else { if (this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null ? (List)this.localCache.getObject(key) : null; if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { Iterator i$ = this.deferredLoads.iterator(); while(i$.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); } this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }
這里我們抓住這樣的一句話
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
進(jìn)入這個方法
privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); List list; try { list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { this.localCache.removeObject(key); } this.localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list; }
我們看到有個一個方法doQuery,進(jìn)入方法看看做了什么。點進(jìn)去后我們發(fā)現(xiàn)是抽象方法,我們選擇simpleExecutor子類查看實現(xiàn)。
publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this.prepareStatement(handler, ms.getStatementLog()); var9 = handler.query(stmt, resultHandler); } finally { this.closeStatement(stmt); } return var9; } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Connection connection = this.getConnection(statementLog); Statement stmt = handler.prepare(connection, this.transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
我們可以看到通過configuration對象的newStatementHandler方法構(gòu)建了一個StatementHandler,然后在調(diào)用prepareStatement方法中獲取連接對象,通過StatementHandler得到Statement對象。另外我們注意到在獲取了Statement對象后調(diào)用了parameterize方法。繼續(xù)跟蹤下去(自行跟蹤哈)我們可以發(fā)現(xiàn)會調(diào)用到ParameterHandler對象的setParameters去處理我們的參數(shù)。所以這里的prepareStatement方法主要使用了StatementHandler和ParameterHandler對象幫助我們處理語句集和參數(shù)的處理。最后還調(diào)用了StatementHandler的query方法,我們繼續(xù)跟蹤下去。
這里我們進(jìn)入到PreparedStatementHandler這個handler查看代碼。
publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { c ps = (PreparedStatement)statement; ps.execute(); return this.resultSetHandler.handleResultSets(ps); }
看到這里,我們終于找到了操作數(shù)據(jù)庫的地方了,就是ps.execute()這句代碼。底層我們可以發(fā)現(xiàn)就是我們平時寫的JDBC!然后將這個執(zhí)行后的PreparedStatement交給resultSetHandler處理結(jié)果集,最后返回我們需要的結(jié)果集。
以上,我們將mybatis的總體運行思路跟大家講解了一遍,很多地方?jīng)]有講到細(xì)節(jié)上,因為本篇主要目的就是帶大家熟悉mybatis總體流程的,細(xì)節(jié)大家可以私底下結(jié)合mybatis的執(zhí)行流程去梳理和理解。
好啦,大家有什么問題都可以在評論區(qū)評論,我看到會盡快回復(fù)噠。哎呀,終于寫完了。拜拜。
噢,對了,這里預(yù)告下,下下篇我將帶大家手寫一遍mybatis!沒錯,純手寫還能跑起來的那種!那下篇呢,下篇當(dāng)然還是講mybatis啦,不過是spring-mybatis!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69593.html
摘要:使用這個類庫中的類將會加載必要的工廠類和類。最終它并不會依賴于或來構(gòu)建應(yīng)用程序代碼。下面對各部分作用總結(jié)下。和無縫整合的機(jī)制和的認(rèn)識在講如何無縫整合進(jìn)之前,我們先認(rèn)識下和這兩個接口的作用。附上上篇博文地址原理概括。 前言 本篇是繼上篇MyBatis原理概括延伸的,所以如果有小伙伴還沒看上篇博文的話,可以先去看下,也不會浪費大家太多的時間,因為本篇會結(jié)合到上篇敘述的相關(guān)內(nèi)容。 好,切入正...
摘要:前言嗨,小伙伴們,這篇博文將帶大家手寫,讓大家對的核心原理以及工作流程有更加深刻的理解。模塊顧名思義,就是框架配置類,用于解析配置文件加載相關(guān)環(huán)境。配置模塊這里的對框架的配置使用了簡單的,主要原因還是簡單易懂然后節(jié)省時間。 前言 (????)??嗨,小伙伴們,這篇博文將帶大家手寫mybatis,讓大家對mybaits的核心原理以及工作流程有更加深刻的理解。在上篇Spring-Mybat...
閱讀 2577·2019-08-30 10:53
閱讀 3183·2019-08-29 16:20
閱讀 2933·2019-08-29 15:35
閱讀 1751·2019-08-29 12:24
閱讀 2865·2019-08-28 18:19
閱讀 1838·2019-08-23 18:07
閱讀 2314·2019-08-23 15:31
閱讀 1158·2019-08-23 14:05