作者:wuxinliulei
鏈接:https://www.zhihu.com/question/25007334/answer/266187562
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括阿拉爾網(wǎng)站建設(shè)、阿拉爾網(wǎng)站制作、阿拉爾網(wǎng)頁制作以及阿拉爾網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,阿拉爾網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到阿拉爾省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
Mybatis原名Ibatis,在2011年從Ibatis2.x升級到Mybatis 3.X,并將項(xiàng)目地址從Apache遷移到了Google code,事實(shí)上我們看MyBatis的類全路徑名,還是保留了Apache和Ibatis的的包前綴
import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;
不過MyBatis的配置文件以及操作類和實(shí)現(xiàn)方式都有了很大變化,這里我們重點(diǎn)講述的是Mybatis,不是Ibatis;
Mybatis的配置文件一共由兩類:
一類用于指定數(shù)據(jù)源、事務(wù)屬性以及其他一些參數(shù)配置信息(通常是一個(gè)獨(dú)立的文件,可以稱之為全局配置文件);
另一類則用于 指定數(shù)據(jù)庫表和程序之間的映射信息(可能不止一個(gè)文件,我們稱之為映射文件)
這些文件的名字并沒有確定的要求;只是要最從特定的dtd的xml文件約束,即xml標(biāo)簽需要符合要求;
上述就是MyBatis的數(shù)據(jù)源,事務(wù)屬性,以及映射文件的索引;
上面是數(shù)據(jù)庫表與程序之間的映射文件,定義了一個(gè)根據(jù)id來獲取User對象的sql
package com.test.domain; /** * users表所對應(yīng)的實(shí)體類 */ public class User { //實(shí)體類的屬性和表的字段名稱一一對應(yīng) 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的,這個(gè)的流程是怎么樣??
// mybatis的配置文件 String resource = "conf.xml"; // 使用類加載器加載mybatis的配置文件(它也加載關(guān)聯(lián)的映射文件) InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource); // 構(gòu)建sqlSession的工廠 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
題主問的sqlmapper可以理解為兩種組件,一種是mapping映射文件,通過id名來獲取相應(yīng)的sql語句,操作數(shù)據(jù)庫;一種是sql的返回對象,
resultType="com.test.domain.User"
這個(gè)就是返回的sql結(jié)果映射成為具體的POJO(Plain Ordinary Java Object)對象;
兩個(gè)重要的類即:
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(); }
在構(gòu)建SqlSessionFactory類的時(shí)候,將會對數(shù)據(jù)源及事務(wù)配置進(jìn)行解析,具體在
org.apache.ibatis.builder.xml.XMLConfigBuilder類
org.apache.ibatis.builder.BaseBuilder類
XMLConfigBuilder類是解析產(chǎn)生org.apache.ibatis.Session.Configuration類的的具體類,Configuration類中將保存中所有的配置;
mybatis的源代碼解析(1)--xml文件解析 - 王久勇 - 博客園
這篇博客介紹了一些xml文件解析的基本;
具體mybatis的xml解析使用到了XPath方式,具體解析過程參看
https:// zhuanlan.zhihu.com/p/31 418285
其實(shí)一般各種輪子都會有一個(gè)解析XML后信息的專用存儲類,比如Config.Java,xxxConf.java,都是在啟動組件時(shí)解析XML配置以用作程序中使用的。
引用網(wǎng)絡(luò)上的一段源代碼
public class Test1 { public static void main(String[] args) throws IOException { //mybatis的配置文件 String resource = "conf.xml"; //使用類加載器加載mybatis的配置文件(它也加載關(guān)聯(lián)的映射文件) InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource); //構(gòu)建sqlSession的工廠 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is); //使用MyBatis提供的Resources類加載mybatis的配置文件(它也加載關(guān)聯(lián)的映射文件) //Reader reader = Resources.getResourceAsReader(resource); //構(gòu)建sqlSession的工廠 //SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader); //創(chuàng)建能執(zhí)行映射文件中sql的sqlSession SqlSession session = sessionFactory.openSession(); /** * 映射sql的標(biāo)識字符串, * me.gacl.mapping.userMapper是userMapper.xml文件中mapper標(biāo)簽的namespace屬性的值, * getUser是select標(biāo)簽的id屬性值,通過select標(biāo)簽的id屬性值就可以找到要執(zhí)行的SQL */ String statement = "me.gacl.mapping.userMapper.getUser";//映射sql的標(biāo)識字符串 //執(zhí)行查詢返回一個(gè)唯一user對象的sql User user = session.selectOne(statement, 1); System.out.println(user); }}
通過跟蹤源代碼可以看到SqlSession通過mapper映射的id來查找數(shù)據(jù)的方法;
org.apache.ibatis.session.defaults.DefaultSqlSession類
publicList selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); List result = executor. 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 MapmappedStatements = new StrictMap ("Mapped Statements collection");
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) { if (validateIncompleteStatements) { buildAllStatements(); } return mappedStatements.get(id); }
其實(shí)就是根據(jù)一個(gè)map映射,key就是定義mapping時(shí)候的id來拿到的;
至此,
------------------------------
上述org.apache.ibatis.session.defaults.DefaultSqlSession類對象中的 selectList方法中的executor對象,
在默認(rèn)情況下,即沒有設(shè)置settings的cache和executor屬性時(shí),默認(rèn)使用的
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; }
所以調(diào)用到了
publicList 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); }
在真正查詢時(shí)先查詢cache,可以看到這個(gè)cache層級在MappedStatement上,也就是在單個(gè)Sql上;若查到,則直接返回,無則通過jdbc查詢,且返回結(jié)果
publicList 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 cachedList = (List ) cache.getObject(key); if (cachedList != null) return cachedList; } finally { cache.getReadWriteLock().readLock().unlock(); } } List list = delegate. 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. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
上述的使用方式是未使用代理的方式,這樣需要我們自行openSession并且關(guān)閉Session;
SqlSession session = null; try { session = sessionFactory.openSession(); /** * 映射sql的標(biāo)識字符串, com.test.mapping.userMapper是userMapper. * xml文件中mapper標(biāo)簽的namespace屬性的值, * getUser是select標(biāo)簽的id屬性值,通過select標(biāo)簽的id屬性值就可以找到要執(zhí)行的SQL */ String statement = "com.test.mapping.userMapper.getUser";// 映射sql的標(biāo)識字符串 // 執(zhí)行查詢返回一個(gè)唯一user對象的sql User user = session.selectOne(statement, 1); System.out.println(user); } catch (Exception e) { // TODO: handle exception } finally { if (session != null) { session.close(); } }
事實(shí)上如果我們使用SqlSessionManager來管理,那么開啟和關(guān)閉Session操作都不用我們來處理了。
final SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(sessionFactory); String statement = "com.test.mapping.userMapper.getUser";// 映射sql的標(biāo)識字符串 User user = sqlSessionManager.selectOne(statement, 1); System.out.println(user);
下面是Interceptor類實(shí)現(xiàn),開啟和關(guān)閉操作都交由了
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,就是利用動態(tài)代理,可以避免我們手寫mapper的id字符串,將查找sql過程和執(zhí)行sql過程放到了代理處理中,更優(yōu)雅些,不過大體流程就是這些,改變了查找sql的步驟,通過Mapper的方法名來查找對應(yīng)的sql的,