Mybatis主线流程源码解析
?Mybatis的基礎(chǔ)使用以及與Spring的相關(guān)集成在官方文檔都寫的非常詳細(xì),但無論我們采用xml還是注解方式在使用的過程中經(jīng)常會(huì)出現(xiàn)各種奇怪的問題,需要花費(fèi)大量的時(shí)間解決。
抽空了解一下Mybatis的相關(guān)源碼還是很有必要。
先來看一個(gè)簡單的Demo:
@Test public void test() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);SqlSession session = sqlSessionFactory.openSession();MethodInfo info = (MethodInfo) session.selectOne("com.ycdhz.mybatis.dao.MethodInfoMapper.selectById", 1);System.out.println(info.toString()); }這個(gè)是官網(wǎng)中入門的一段代碼,我根據(jù)自己的情況做了一些參數(shù)上的改動(dòng)。這段代碼很容易理解,解析一個(gè)xml文件,通過SqlSessionFactoryBuilder構(gòu)建一個(gè)SqlSessionFactory實(shí)例。
拿到了SqlSessionFactory我們就可以獲取SqlSession。SqlSession 包含了面向數(shù)據(jù)庫執(zhí)行 SQL 命令所需的所有方法,所以我們可以通過 SqlSession 實(shí)例來直接執(zhí)行已映射的 SQL 語句。
代碼很簡單的展示了Mybatis到底是什么,有什么作用。
?
Mybatis主線流程:解析Configuration返回SqlSessionFactory;拿到SqlSession對(duì)執(zhí)行器進(jìn)行初始化 SimpleExecutor;操作數(shù)據(jù)庫;
?
我們先來看一下mybatis-config.xml,在這個(gè)xml中包含了Mybatis的核心設(shè)置,有獲取數(shù)據(jù)庫連接實(shí)例的數(shù)據(jù)源(DataSource)和決定事務(wù)作用域和控制方式的事務(wù)管理器(TransactionManager)等等。 具體的配置信息可以參考官方文檔。需要注意的是Xml的屬性配置有一定的順序要求,具體的可以查看http://mybatis.org/dtd/mybatis-3-config.dtd。 <!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mytest"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><mapper resource="mybatis/MethodInfoMapper.xml"/><!--<mapper class="com.ycdhz.mybatis.dao.MethodInfoMapper" />--><!--<package name="com.ycdhz.mybatis.dao" />--></mappers> </configuration>test()前兩行主要是通過流來讀取配置文件,我們直接從new SqlSessionFactoryBuilder().build(inputStream)這段代碼開始:
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// SqlSessionFactoryBuilde拿到輸入流后,構(gòu)建了一個(gè)XmlConfigBuilder的實(shí)例。通過parse()進(jìn)行解析XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {//XmlConfigBuilder.parse()解析完后將數(shù)據(jù)傳給DefaultSqlSessionFactoryreturn new DefaultSqlSessionFactory(config);} }public class XMLConfigBuilder extends BaseBuilder {private boolean parsed;private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration());ErrorContext.instance().resource("SQL Mapper Configuration");this.parsed = false;}public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;
//這個(gè)方法主要就是解析xml文件了parseConfiguration(parser.evalNode("/configuration"));return configuration;}private void parseConfiguration(XNode root) {try {//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}} }
? mybatis-config.xml 文件中的mapper屬性支持四種配置方式,但是只有package,class這兩種發(fā)式支持通過注解來配置和映射原生信息(原因在于configuration.addMappers()這個(gè)方法)。
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}? addMappers()調(diào)用了MapperAnnotationBuilder.parse()這樣一段代碼。我們發(fā)現(xiàn)當(dāng)resource不為空的時(shí)候,代碼首先會(huì)調(diào)用loadXmlResource()去Resource文件夾下查找(com/ycdhz/mybatis/dao/MethodInfoMapper.xml),
如果發(fā)現(xiàn)當(dāng)前文件就加載。但實(shí)際這個(gè)時(shí)候type信息來自注解,MethodInfoMapper.xml在容器中被加載兩次。所以Configruation下的靜態(tài)類StrictMap.put()時(shí)會(huì)拋出一個(gè) Mapped Statements collection already contains value for com.ycdhz.mybatis.dao.MethodInfoMapper.selectById 的異常
public class MapperAnnotationBuilder {public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());parseCache();parseCacheRef();Method[] methods = type.getMethods();for (Method method : methods) {try {// issue #237if (!method.isBridge()) {parseStatement(method);}} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}private void loadXmlResource() {// Spring may not know the real resource name so we check a flag// to prevent loading again a resource twice// this flag is set at XMLMapperBuilder#bindMapperForNamespaceif (!configuration.isResourceLoaded("namespace:" + type.getName())) {String xmlResource = type.getName().replace('.', '/') + ".xml";InputStream inputStream = null;try {inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);} catch (IOException e) {// ignore, resource is not required}if (inputStream != null) {XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());xmlParser.parse();}}} }
這個(gè)時(shí)候我們已經(jīng)完成了xml文件的解析過程,拿到了DefaultSqlSessionFactory。下面我們?cè)賮砜匆幌聅qlSessionFactory.openSession()的過程:
public class DefaultSqlSessionFactory implements SqlSessionFactory{private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}} }public class Configuration {protected boolean cacheEnabled = true;protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;protected final InterceptorChain interceptorChain = new InterceptorChain();public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;} }
openSession()方法會(huì)調(diào)用DefaultSqlSessionFactory.openSessionFromDataSource()方法,在這個(gè)方法中會(huì)開啟事務(wù)、創(chuàng)建了執(zhí)行器Executor:
MyBatis的事務(wù)管理分為兩種形式(配置mybatis-config.xml文件的transactionManager屬性):
1)使用JDBC的事務(wù)管理機(jī)制:即利用java.sql.Connection對(duì)象完成對(duì)事務(wù)的提交(commit())、回滾(rollback())、關(guān)閉(close())等
2)使用MANAGED的事務(wù)管理機(jī)制:這種機(jī)制MyBatis自身不會(huì)去實(shí)現(xiàn)事務(wù)管理,而是讓程序的容器如(JBOSS,Weblogic)來實(shí)現(xiàn)對(duì)事務(wù)的管理
?
? 執(zhí)行器ExecutorType分為三類(默認(rèn)使用的是ExecutorType.SIMPLE):
1)ExecutorType.SIMPLE: 這個(gè)執(zhí)行器類型不做特殊的事情。它為每個(gè)語句的執(zhí)行創(chuàng)建一個(gè)新的預(yù)處理語句。
2)ExecutorType.REUSE: 這個(gè)執(zhí)行器類型會(huì)復(fù)用預(yù)處理語句。
3)ExecutorType.BATCH: 這個(gè)執(zhí)行器會(huì)批量執(zhí)行所有更新語句,如果 SELECT 在它們中間執(zhí)行還會(huì)標(biāo)定它們是 必須的,來保證一個(gè)簡單并易于理解的行為。?
? ? ? 因?yàn)镸ybatis的一級(jí)緩存是默認(rèn)開啟的,查看newExecutor()不難發(fā)現(xiàn),最后通過CachingExecutor對(duì)SimpleExecutor進(jìn)行了裝飾(詳細(xì)代碼可以查看https://www.cnblogs.com/jiangyaxiong1990/p/9236764.html)
Mybatis緩存設(shè)計(jì)成兩級(jí)結(jié)構(gòu),分為一級(jí)緩存、二級(jí)緩存:(參考 https://blog.csdn.net/luanlouis/article/details/41280959 )
1)一級(jí)緩存是Session會(huì)話級(jí)別的緩存,位于表示一次數(shù)據(jù)庫會(huì)話的SqlSession對(duì)象之中,又被稱之為本地緩存。默認(rèn)情況下自動(dòng)開啟,用戶沒有定制它的權(quán)利(不過這也不是絕對(duì)的,可以通過開發(fā)插件對(duì)它進(jìn)行修改);
實(shí)際上MyBatis的一級(jí)緩存是使用PerpetualCache來維護(hù)的,PerpetualCache實(shí)現(xiàn)原理其實(shí)很簡單,其內(nèi)部就是通過一個(gè)簡單的HashMap<k,v>?來實(shí)現(xiàn)的,沒有其他的任何限制。
2)二級(jí)緩存是Application應(yīng)用級(jí)別的緩存,它的是生命周期很長,跟Application的聲明周期一樣,也就是說它的作用范圍是整個(gè)Application應(yīng)用。
???? ?MyBatis的二級(jí)緩存設(shè)計(jì)得比較靈活,你可以使用MyBatis自己定義的二級(jí)緩存實(shí)現(xiàn);你也可以通過實(shí)現(xiàn)org.apache.ibatis.cache.Cache接口自定義緩存;也可以使用第三方內(nèi)存緩存庫,如Redis等
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/jiangyaxiong1990/p/10306981.html
總結(jié)
以上是生活随笔為你收集整理的Mybatis主线流程源码解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js第一天
- 下一篇: django系列5.1--ORM对数据库