Mybatis的基礎使用以及與Spring的相關集成在官方文檔都寫的很是詳細,但不管咱們採用xml仍是註解方式在使用的過程當中常常會出現各類奇怪的問題,須要花費大量的時間解決。html
抽空了解一下Mybatis的相關源碼仍是頗有必要。java
先來看一個簡單的Demo:mysql
@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()); }
這個是官網中入門的一段代碼,我根據本身的狀況作了一些參數上的改動。這段代碼很容易理解,解析一個xml文件,經過SqlSessionFactoryBuilder構建一個SqlSessionFactory實例。sql
拿到了SqlSessionFactory咱們就能夠獲取SqlSession。SqlSession 包含了面向數據庫執行 SQL 命令所需的全部方法,因此咱們能夠經過 SqlSession 實例來直接執行已映射的 SQL 語句。數據庫
代碼很簡單的展現了Mybatis究竟是什麼,有什麼做用。apache
Mybatis主線流程:解析Configuration返回SqlSessionFactory;拿到SqlSession對執行器進行初始化 SimpleExecutor;操做數據庫;緩存
咱們先來看一下mybatis-config.xml,在這個xml中包含了Mybatis的核心設置,有獲取數據庫鏈接實例的數據源(DataSource)和決定事務做用域和控制方式的事務管理器(TransactionManager)等等。
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<?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> <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)這段代碼開始:session
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // SqlSessionFactoryBuilde拿到輸入流後,構建了一個XmlConfigBuilder的實例。經過parse()進行解析 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()解析完後將數據傳給DefaultSqlSessionFactory return 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;
//這個方法主要就是解析xml文件了 parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(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 #631 environmentsElement(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這兩種髮式支持經過註解來配置和映射原生信息(緣由在於configuration.addMappers()這個方法)。mybatis
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()調用了MapperAnnotationBuilder.parse()這樣一段代碼。咱們發現當resource不爲空的時候,代碼首先會調用loadXmlResource()去Resource文件夾下查找(com/ycdhz/mybatis/dao/MethodInfoMapper.xml),app
若是發現當前文件就加載。但實際這個時候type信息來自注解,MethodInfoMapper.xml在容器中被加載兩次。因此Configruation下的靜態類StrictMap.put()時會拋出一個 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 #237 if (!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#bindMapperForNamespace if (!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(); } } } }
這個時候咱們已經完成了xml文件的解析過程,拿到了DefaultSqlSessionFactory。下面咱們再來看一下sqlSessionFactory.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()方法會調用DefaultSqlSessionFactory.openSessionFromDataSource()方法,在這個方法中會開啓事務、建立了執行器Executor:
MyBatis的事務管理分爲兩種形式(配置mybatis-config.xml文件的transactionManager屬性):
1)使用JDBC的事務管理機制:即利用java.sql.Connection對象完成對事務的提交(commit())、回滾(rollback())、關閉(close())等
2)使用MANAGED的事務管理機制:這種機制MyBatis自身不會去實現事務管理,而是讓程序的容器如(JBOSS,Weblogic)來實現對事務的管理
執行器ExecutorType分爲三類(默認使用的是ExecutorType.SIMPLE):
1)ExecutorType.SIMPLE: 這個執行器類型不作特殊的事情。它爲每一個語句的執行建立一個新的預處理語句。
2)ExecutorType.REUSE: 這個執行器類型會複用預處理語句。
3)ExecutorType.BATCH: 這個執行器會批量執行全部更新語句,若是 SELECT 在它們中間執行還會標定它們是 必須的,來保證一個簡單並易於理解的行爲。
由於Mybatis的一級緩存是默認開啓的,查看newExecutor()不難發現,最後經過CachingExecutor對SimpleExecutor進行了裝飾(詳細代碼能夠查看https://www.cnblogs.com/jiangyaxiong1990/p/9236764.html)
Mybatis緩存設計成兩級結構,分爲一級緩存、二級緩存:(參考 https://blog.csdn.net/luanlouis/article/details/41280959 )
1)一級緩存是Session會話級別的緩存,位於表示一次數據庫會話的SqlSession對象之中,又被稱之爲本地緩存。默認狀況下自動開啓,用戶沒有定製它的權利(不過這也不是絕對的,能夠經過開發插件對它進行修改);
實際上MyBatis的一級緩存是使用PerpetualCache來維護的,PerpetualCache實現原理其實很簡單,其內部就是經過一個簡單的HashMap<k,v> 來實現的,沒有其餘的任何限制。
2)二級緩存是Application應用級別的緩存,它的是生命週期很長,跟Application的聲明週期同樣,也就是說它的做用範圍是整個Application應用。
MyBatis的二級緩存設計得比較靈活,你可使用MyBatis本身定義的二級緩存實現;你也能夠經過實現org.apache.ibatis.cache.Cache接口自定義緩存;也可使用第三方內存緩存庫,如Redis等