Mybatis工作原理
作者:wuxinliulei
連結:
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
Mybatis原名Ibatis,在2011年從Ibatis2.x升級到Mybatis 3.X,並將專案地址從Apache遷移到了Google code,事實上我們看MyBatis的類全路徑名,還是保留了Apache和Ibatis的的包字首
import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;
不過MyBatis的配置檔案以及操作類和實現方式都有了很大變化,這裡我們重點講述的是Mybatis,不是Ibatis;
Mybatis的配置檔案一共由兩類:
一類用於指定資料來源、事務屬性以及其他一些引數配置資訊(通常是一個獨立的檔案,可以稱之為全域性配置檔案);
另一類則用於 指定資料庫表和程式之間的對映資訊(可能不止一個檔案,我們稱之為對映檔案)
這些檔案的名字並沒有確定的要求;只是要最從特定的dtd的xml檔案約束,即xml標籤需要符合要求;
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" " <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/mybatis" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <mappers> <!-- 註冊userMapper.xml檔案, userMapper.xml位於com.test.mapping這個包下,所以resource寫成com/test/mapping/userMapper.xml--> <mapper resource="com/test/mapping/userMapper.xml"/> </mappers> </configuration>
上述就是MyBatis的資料來源,事務屬性,以及對映檔案的索引;
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" " <!-- 為這個mapper指定一個唯一的namespace,namespace的值習慣上設定成包名+sql對映檔名,這樣就能夠保證namespace的值是唯一的 例如namespace="com.test.mapping.userMapper"就是com.test.mapping(包名)+userMapper(userMapper.xml檔案去除字尾) --> <mapper namespace="com.test.mapping.userMapper"> <!-- 根據id查詢得到一個user物件 --> <select id="getUser" parameterType="int" resultType="com.test.domain.User"> select * from users where id=#{id} </select> </mapper>
上面是資料庫表與程式之間的對映檔案,定義了一個根據id來獲取User物件的sql
package com.test.domain; /** * users表所對應的實體類 */ public class User { //實體類的屬性和表的欄位名稱一一對應 private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
問題:
mybatis是怎麼在程式中順利的找到sqlmapper的,這個的流程是怎麼樣??
// mybatis的配置檔案 String resource = "conf.xml"; // 使用類載入器載入mybatis的配置檔案(它也載入關聯的對映檔案) InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource); // 構建sqlSession的工廠 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
題主問的sqlmapper可以理解為兩種元件,一種是mapping對映檔案,透過id名來獲取相應的sql語句,運算元據庫;一種是sql的返回物件,
resultType="com.test.domain.User"
這個就是返回的sql結果對映成為具體的POJO(Plain Ordinary Java Object)物件;
兩個重要的類即:
org.apache.ibatis.session.SqlSessionFactory;
org.apache.ibatis.session.SqlSession;
package org.apache.ibatis.session; import java.sql.Connection; public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }
在構建SqlSessionFactory類的時候,將會對資料來源及事務配置進行解析,具體在
org.apache.ibatis.builder.xml.XMLConfigBuilder類
org.apache.ibatis.builder.BaseBuilder類
XMLConfigBuilder類是解析產生org.apache.ibatis.Session.Configuration類的的具體類,Configuration類中將儲存中所有的配置;
mybatis的原始碼解析(1)--xml檔案解析 - 王久勇 - 部落格園
這篇部落格介紹了一些xml檔案解析的基本;
具體mybatis的xml解析使用到了XPath方式,具體解析過程參看
其實一般各種輪子都會有一個解析XML後資訊的專用儲存類,比如Config.Java,xxxConf.java,都是在啟動元件時解析XML配置以用作程式中使用的。
引用網路上的一段原始碼
public class Test1 { public static void main(String[] args) throws IOException { //mybatis的配置檔案 String resource = "conf.xml"; //使用類載入器載入mybatis的配置檔案(它也載入關聯的對映檔案) InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource); //構建sqlSession的工廠 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is); //使用MyBatis提供的Resources類載入mybatis的配置檔案(它也載入關聯的對映檔案) //Reader reader = Resources.getResourceAsReader(resource); //構建sqlSession的工廠 //SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); //建立能執行對映檔案中sql的sqlSession SqlSession session = sessionFactory.openSession(); /** * 對映sql的標識字串, * me.gacl.mapping.userMapper是userMapper.xml檔案中mapper標籤的namespace屬性的值, * getUser是select標籤的id屬性值,透過select標籤的id屬性值就可以找到要執行的SQL */ String statement = "me.gacl.mapping.userMapper.getUser";//對映sql的標識字串 //執行查詢返回一個唯一user物件的sql User user = session.selectOne(statement, 1); System.out.println(user); }}
透過跟蹤原始碼可以看到SqlSession透過mapper對映的id來查詢資料的方法;
org.apache.ibatis.session.defaults.DefaultSqlSession類
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List<E> result = executor.<E> query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); return result; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
org.apache.ibatis.session.Configuration類
public MappedStatement getMappedStatement(String id) { return this.getMappedStatement(id, true); }
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); }
其實就是根據一個map對映,key就是定義mapping時候的id來拿到的;
至此,
------------------------------
上述org.apache.ibatis.session.defaults.DefaultSqlSession類物件中的 selectList方法中的executor物件,
在預設情況下,即沒有設定settings的cache和executor屬性時,預設使用的
org.apache.ibatis.executor.CachingExecutor類
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) { 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, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
所以呼叫到了
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
在真正查詢時先查詢cache,可以看到這個cache層級在MappedStatement上,也就是在單個Sql上;若查到,則直接返回,無則透過jdbc查詢,且返回結果
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, key, parameterObject, boundSql); if (!dirty) { cache.getReadWriteLock().readLock().lock(); try { @SuppressWarnings("unchecked") List<E> cachedList = (List<E>) cache.getObject(key); if (cachedList != null) return cachedList; } finally { cache.getReadWriteLock().readLock().unlock(); } } List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578. Query must be // not synchronized to // prevent deadlocks return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
上述的使用方式是未使用代理的方式,這樣需要我們自行openSession並且關閉Session;
SqlSession session = null; try { session = sessionFactory.openSession(); /** * 對映sql的標識字串, com.test.mapping.userMapper是userMapper. * xml檔案中mapper標籤的namespace屬性的值, * getUser是select標籤的id屬性值,透過select標籤的id屬性值就可以找到要執行的SQL */ String statement = "com.test.mapping.userMapper.getUser";// 對映sql的標識字串 // 執行查詢返回一個唯一user物件的sql User user = session.selectOne(statement, 1); System.out.println(user); } catch (Exception e) { // TODO: handle exception } finally { if (session != null) { session.close(); } }
事實上如果我們使用SqlSessionManager來管理,那麼開啟和關閉Session操作都不用我們來處理了。
final SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(sessionFactory); String statement = "com.test.mapping.userMapper.getUser";// 對映sql的標識字串 User user = sqlSessionManager.selectOne(statement, 1); System.out.println(user);
下面是Interceptor類實現,開啟和關閉操作都交由了
private class SqlSessionInterceptor implements InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get(); if (sqlSession != null) { try { return method.invoke(sqlSession, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } else { final SqlSession autoSqlSession = openSession(); try { final Object result = method.invoke(autoSqlSession, args); autoSqlSession.commit(); return result; } catch (Throwable t) { autoSqlSession.rollback(); throw ExceptionUtil.unwrapThrowable(t); } finally { autoSqlSession.close(); } } } }
如果使用Mapper方式來操作SQL,就是利用動態代理,可以避免我們手寫mapper的id字串,將查詢sql過程和執行sql過程放到了代理處理中,更優雅些,不過大體流程就是這些,改變了查詢sql的步驟,透過Mapper的方法名來查詢對應的sql的,
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31553506/viewspace-2215628/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從原始碼角度分析 MyBatis 工作原理原始碼MyBatis
- 面試官:你分析過mybatis工作原理嗎?面試MyBatis
- MyBatis原理MyBatis
- Mybaitis入門基礎(一)MyBatis的概念引入及工作原理AIMyBatis
- Mybatis原理和SqlSessionMyBatisSQLSession
- MyBatis原理解析MyBatis
- MyBatis介面繫結原理MyBatis
- 面試被問MyBatis原理?面試MyBatis
- 深入淺出MyBatis:MyBatis解析和執行原理MyBatis
- require工作原理UI
- Nginx工作原理Nginx
- Handler 工作原理
- rman工作原理
- mydumper工作原理
- pr工作原理
- MyBatis框架原理3:快取MyBatis框架快取
- MyBatis-07-外掛原理MyBatis
- Mybatis是怎麼工作的(二)MyBatis
- MyBatis動態代理執行原理MyBatis
- mybatis系列第1篇:框架原理MyBatis框架
- MyBatis的解析和執行原理MyBatis
- [Spring]MyBatis的執行原理SpringMyBatis
- javascript引擎工作原理JavaScript
- ZStack基本工作原理
- Mirror 的工作原理
- Web Scraper工作原理Web
- HTTPS工作原理HTTP
- SpringMVC工作原理SpringMVC
- Spark的工作原理Spark
- Docker 工作原理分析Docker
- panic/recover工作原理
- 【MySQL】Mydumper工作原理MySql
- View的工作原理View
- HashMap的工作原理HashMap
- DHCP的工作原理
- tcmalloc的工作原理
- 相機工作原理
- undo的工作原理