在文章:Mybatis源碼解析,一步一步從淺入深(一):建立準備工程,中咱們爲了解析mybatis源碼建立了一個mybatis的簡單工程(源碼已上傳github,連接在文章末尾),並實現了一個查詢功能。接下來就順着查詢功能的實現開始一步一步開始解析mybatis源碼。html
首先們觀察咱們的測試代碼類UserDaoTest:java
package com.test.learnmybatis; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import com.zcz.learnmybatis.dao.UserDao; import com.zcz.learnmybatis.entity.User; import junit.framework.Assert; public class UserDaoTest { @Test public void finUserById() { //2,獲取SqlSession SqlSession sqlSession = getSessionFactory().openSession(); //3,獲取UserDao代理類 UserDao userMapper = sqlSession.getMapper(UserDao.class); //4,執行查詢 User user = userMapper.findUserById(1); Assert.assertNotNull("not find", user); } /** * 1,獲取SqlSessionFactory * @return */ private static SqlSessionFactory getSessionFactory() { SqlSessionFactory sessionFactory = null; //配置文件名稱 String resource = "configuration.xml"; try { //使用配置文件構造SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource)); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return sessionFactory; } }
整個過程能夠分爲四個步驟git
1,獲取SqlSessionFactorygithub
2,獲取SqlSessionsql
3,獲取UserDao代理類apache
4,執行查詢設計模式
我接下來也會根據這四步進行源碼的解析。session
一,獲取SqlSessionFactorymybatis
先來看一下靜態方法getSessionFactory。在這個方法中咱們讀取configuration.xml,並使用configuration.xml配置文件實例化了一個SqlSessionFactory。app
/** * 獲取SqlSessionFactory * @return */ private static SqlSessionFactory getSessionFactory() { SqlSessionFactory sessionFactory = null; //配置文件名稱 String resource = "configuration.xml"; try { //使用配置文件構造SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource)); }catch (Exception e) { // TODO: handle exception e.printStackTrace(); } return sessionFactory; }
代碼:sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader(resource));
相信你們都能看懂這段代碼:
1,Resources.getResourceAsReader(resource),讀取了配置文件configuration.xml,並返回一個字符輸入流(Reader),這樣configuration.xml中的配置信息就都被讀取到了一個字符輸入流中了。
public static Reader getResourceAsReader(String resource)方法不過多解釋,你們又須要的話,我就在寫一篇文章去詳細闡述。
2,使用new關鍵字建立了一個SqlSessionFactoryBuilder的匿名對象。
3,調用匿名對象的build(Reader reader)方法,並將1中的字符輸入流做爲參數傳入。
這樣SqlSessionFactory對象就建立成功了,接下來咱們詳細的分析一下build(Reader reader)方法,先看源碼
這裏直接調用了public SqlSessionFactory build(Reader reader, String environment, Properties properties)方法,而且environment和properties是null;
方法詳情以下:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { //構造(XML配置解析器)XMLConfigBuilder對象 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //調用build方法並返回SqlSessionFactory return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
代碼:XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
這段代碼的做用是使用上一步讀取的configuration.xml字符輸入流,實例化一個xml配置解析器(XMLConfigBuilder),而且咱們已經知道 environment, properties的值爲null;
在XMLConfigBuilder對象的實例化過程當中初始化一個很是重要的對象:Configuration,而Configuration對象承載了configuration.xml中的全部配置內容,具體的初始化內容請查看:Mybatis源碼解析,一步一步從淺入深(三):實例化xml配置解析器(XMLConfigBuilder)
代碼:return build(parser.parse());
1,parser.parse()返回一個Configuration對象實例,這是一個及其重要的方法,由於解析configuration.xml並生成Configuration對象,都是在這個方法裏完成的。具體的執行細節,請查閱:Mybatis源碼解析,一步一步從淺入深(四):將configuration.xml的解析到Configuration對象實例,Mybatis源碼解析,一步一步從淺入深(五):mapper節點的解析
2,調用SqlSessionFactoryBuilder的public SqlSessionFactory build(Configuration config)方法建立一個SqlSessionFactory對象實例,咱們來看一下build(Configuration config)方法的內容:
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
在這個方法中,使用parser.parse()解析出來的Configuration對象實例做爲參數,實例化了一個DefaultSqlSessionFactory類的對象。
咱們看一下類DefaultSqlSessionFactory的聲明:
public class DefaultSqlSessionFactory implements SqlSessionFactory { }
DefaultSqlSessionFactory類實現了SqlSessionFactory接口,咱們知道java能夠經過多態讓SqlSessionFactory 父類引用指向DefaultSqlSessionFactory子類實例。
到這裏咱們的SqlSessionFactory就建立完成了。
二,獲取SqlSession
關鍵代碼:SqlSession sqlSession = getSessionFactory().openSession();
在上一步咱們瞭解到,getSessionFactory()方法,返回一個DefaultSqlSessionFactory實例對象,那麼接下來咱們就看一下這個DefaultSqlSessionFactory的openSession方法:
public SqlSession openSession() { //直接調用了openSessionFromDataSource方法 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
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); // 實例化sql執行器 final Executor executor = configuration.newExecutor(tx, execType); // 返回默認SqlSession 實例化對象 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(); } }
// 從運行環境中獲取事務工廠 private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { return new ManagedTransactionFactory(); } return environment.getTransactionFactory(); }
到這裏SqlSession也獲取成功了,獲取到的是DefaultSqlSession的示例對象。
三,獲取UserDao代理類
關鍵代碼:UserDao userMapper = sqlSession.getMapper(UserDao.class);
從上一步知道這裏的sqlSession是DefaultSqlSession的實例化對象,那麼就來看一下getMapper方法的源碼:
public <T> T getMapper(Class<T> type) { // 這裏的configuration就是一開始一直陪伴着咱們的那個Configuration實例對象 return configuration.<T>getMapper(type, this); }
在這裏就有幾個奇怪的問題:
1,爲何在之前的代碼流程中歷來沒有addMapper,而這裏卻有getMapper?
2,UserDao明明是咱們定義的一個接口類,根本沒有定義實現類,那這個userMapper是什麼?是mybatis自動爲咱們生成的實現類嗎?
帶着這兩個問題,咱們在文章Mybatis源碼解析,一步一步從淺入深(六):映射代理類的獲取進行了詳細分析。
四,執行查詢
經上一系列的分析,已經基本明確了configuration.xml,userDao-mapping.xml文件的解析,映射代理類實例化對象的生成的整個過程。接下來就剩餘最後一個步驟:執行查詢。一塊兒來看一下吧。
關鍵代碼:User user = userMapper.findUserById(1);
看過文章Mybatis源碼解析,一步一步從淺入深(六):映射代理類的獲取的同窗們應該已經知道了,代理類對象在執行方法的時候,是調用了InvocationHandler實現類的Invoke方法:
1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2 if (Object.class.equals(method.getDeclaringClass())) { 3 try { 4 return method.invoke(this, args); 5 } catch (Throwable t) { 6 throw ExceptionUtil.unwrapThrowable(t); 7 } 8 } 9 final MapperMethod mapperMethod = cachedMapperMethod(method); 10 return mapperMethod.execute(sqlSession, args); 11 }
而真正執行查詢的代碼就在第10行,即:mapperMethod.execute(sqlSession, args);看源碼:
1 //根據查詢類型執行不一樣的方法 2 public Object execute(SqlSession sqlSession, Object[] args) { 3 Object result; 4 if (SqlCommandType.INSERT == command.getType()) { 5 //insert 6 Object param = method.convertArgsToSqlCommandParam(args); 7 result = rowCountResult(sqlSession.insert(command.getName(), param)); 8 } else if (SqlCommandType.UPDATE == command.getType()) { 9 //update 10 Object param = method.convertArgsToSqlCommandParam(args); 11 result = rowCountResult(sqlSession.update(command.getName(), param)); 12 } else if (SqlCommandType.DELETE == command.getType()) { 13 //delete 14 Object param = method.convertArgsToSqlCommandParam(args); 15 result = rowCountResult(sqlSession.delete(command.getName(), param)); 16 } else if (SqlCommandType.SELECT == command.getType()) { 17 //select 18 if (method.returnsVoid() && method.hasResultHandler()) { 19 executeWithResultHandler(sqlSession, args); 20 result = null; 21 } else if (method.returnsMany()) { 22 result = executeForMany(sqlSession, args); 23 } else if (method.returnsMap()) { 24 result = executeForMap(sqlSession, args); 25 } else { 26 //咱們的代碼執行的是這裏 27 Object param = method.convertArgsToSqlCommandParam(args); 28 result = sqlSession.selectOne(command.getName(), param); 29 } 30 } else { 31 throw new BindingException("Unknown execution method for: " + command.getName()); 32 } 33 if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 34 throw new BindingException("Mapper method '" + command.getName() 35 + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); 36 } 37 return result; 38 }
關鍵代碼:result = sqlSession.selectOne(command.getName(), param);
別看這只是一行簡單的代碼,其實這句代碼的背後有不少的邏輯操做,我在文章:Mybatis源碼解析,一步一步從淺入深(七):執行查詢中進行了詳細的分析。請你們查閱;
源代碼中的第28行代碼的執行結果就是咱們指望的查詢結果了。
五,總體結束
到這裏,mybatis的源碼解析系列文章就正式結束了,咱們的示例工程很簡單,只有一個Mapper文件,而且只有一個根據Id查詢的功能。這整個系列的文章,就是根據這個簡單的工程,經過斷點調試的方法,一步一步的跟蹤源代碼,最後明確的這個簡單的查詢是如何執行的。固然mybais還有更多的功能,例如insert,update等,其餘的標籤,其餘的功能,都沒有在本系列的文章中闡述,同時在mybatis中對設計模式的使用也是恰到好處,並且由於做者的水平,時間,精力有限。只能爲你們大概的介紹了一下。解釋不夠準確,信息不夠豐富也是本系列文章的一大遺憾,可是沒有關係,我會在接下來的學習和感悟中繼續補充並完善這一些列的文章,供你們閱讀參考。
「學習如逆水行舟,不進則退」
這句話送給你們,以期共勉。
原創不易,轉載請聲明出處:http://www.javashuo.com/article/p-skvwktcj-cg.html