mybatis深入剖析

Java中提到持久層框架,相信沒有人不知道mybatis的存在,相對於JDBC她多了一份幹練(jdbc工作量大),相對於Hibernate她又多了一份靈動(HQL雖然方便,但太呆板)。今天我們就一起走進她的世界。

一、mybatis的簡單實現

準備工作

1.創建實體類和表映射

2.導入maven依賴

JAVA學習筆記----mybatis深入剖析

編寫接口和mapper爲文件

JAVA學習筆記----mybatis深入剖析

JAVA學習筆記----mybatis深入剖析

JAVA學習筆記----mybatis深入剖析

注意:在idea中,直接把資源文件放在src文件夾下,如果不進行設置,是不能被找到的,所以一定要在pom.xml(第一張圖)中加入標記的代碼,指定加載位置。

編寫配置文件

JAVA學習筆記----mybatis深入剖析

JAVA學習筆記----mybatis深入剖析

注意:配置文件要放在resources目錄下

測試

JAVA學習筆記----mybatis深入剖析

二、mybatis的實現原理分析

原理分析之一:MyBatis的主要成員

  • Configuration :MyBatis所有的配置信息都保存在Configuration對象之中,配置文件中的大部分配置都會存儲到該類中。

  • SqlSession :作爲MyBatis工作的主要頂層API,表示和數據庫交互時的會話,完成必要數據庫增刪改查功能。

  • Executor: MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護。

  • StatementHandler :封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數等。

  • ParameterHandler :負責對用戶傳遞的參數轉換成JDBC Statement 所對應的數據類型。

  • ResultSetHandler : 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合。

  • TypeHandler :負責java數據類型和jdbc數據類型(也可以說是數據表列類型)之間的映射和轉換。

  • MappedStatement :MappedStatement維護一條<select|update|delete|insert>節點的封裝。

  • SqlSource:負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回。

  • BoundSql :表示動態生成的SQL語句以及相應的參數信息。

原理分析之二:Mybatis的執行流程

JAVA學習筆記----mybatis深入剖析

原理分析之三:初始化(配置文件讀取和解析)

1. 準備工作

編寫測試代碼(具體請參考《Mybatis入門示例》),設置斷點,以Debug模式運行,具體代碼如下:

Java代碼

String resource = "mybatis.cfg.xml"; Reader reader = Resources.getResourceAsReader(resource); SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader); SqlSession session = ssf.openSession();

我們此次就對上面的代碼進行跟蹤和分析,let's go。

首先我們按照順序先看看第一行和第二行代碼,看看它主要完成什麼事情:

Java代碼

String resource = "mybatis.cfg.xml"; Reader reader = Resources.getResourceAsReader(resource);

讀取Mybaits的主配置配置文件,並返回該文件的輸入流,我們知道Mybatis所有的SQL語句都寫在XML配置文件裏面,所以第一步就需要讀取這些XML配置文件,這個不難理解,關鍵是讀取文件後怎麼存放。

我們接着看第三行代碼(如下),該代碼主要是讀取配置文件流並將這些配置信息存放到Configuration類中。

Java代碼

SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);

SqlSessionFactoryBuilder的build的方法如下:

Java代碼

public SqlSessionFactory build(Reader reader) { return build(reader, null, null); }

其實是調用該類的另一個build方法來執行的,具體代碼如下:

Java代碼

public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); 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. } } }

我們重點看一下里面兩行:

Java代碼

//創建一個配置文件流的解析對象XMLConfigBuilder,其實這裏是將環境和配置文件流賦予解析類 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); // 解析類對配置文件進行解析並將解析的內容存放到Configuration對象中,並返回SqlSessionFactory return build(parser.parse());

這裏的XMLConfigBuilder初始化其實調用的代碼如下:

Java代碼

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }

XMLConfigBuilder的parse方法執行代碼如下:

Java代碼

public Configuration parse() { if (parsed) { throw new BuilderException("Each MapperConfigParser can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }

解析的內容主要是在parseConfiguration方法中,它主要完成的工作是讀取配置文件的各個節點,然後將這些數據映射到內存配置對象Configuration中,我們看一下parseConfiguration方法內容:

Java代碼

private void parseConfiguration(XNode root) { try { typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); propertiesElement(root.evalNode("properties")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); }catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }

最後的build方法其實是傳入配置對象進去,創建DefaultSqlSessionFactory實例出來. DefaultSqlSessionFactory是SqlSessionFactory的默認實現.

Java代碼

public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }

最後我們看一下第四行代碼:

Java代碼

SqlSession session = ssf.openSession();

通過調用DefaultSqlSessionFactory的openSession方法返回一個SqlSession實例,我們看一下具體是怎麼得到一個SqlSession實例的。首先調用openSessionFromDataSource方法。

Java代碼

public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }

下面我們看一下openSessionFromDataSource方法的邏輯:

Java代碼

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Connection connection = null; try { //獲取配置信息裏面的環境信息,這些環境信息都是包括使用哪種數據庫,連接數據庫的信息,事務 final Environment environment = configuration.getEnvironment(); //根據環境信息關於數據庫的配置獲取數據源 final DataSource dataSource = getDataSourceFromEnvironment(environment); //根據環境信息關於事務的配置獲取事務工廠 TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); connection = dataSource.getConnection(); if (level != null) { //設置連接的事務隔離級別 connection.setTransactionIsolation(level.getLevel()); } //對connection進行包裝,使連接具備日誌功能,這裏用的是代理。 connection = wrapConnection(connection);//從事務工廠獲取一個事務實例 Transaction tx = transactionFactory.newTransaction(connection, autoCommit); //從配置信息中獲取一個執行器實例 Executor executor = configuration.newExecutor(tx, execType); //返回SqlSession的一個默認實例 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeConnection(connection); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

傳入參數說明:

(1)ExecutorType:執行類型,ExecutorType主要有三種類型:SIMPLE, REUSE, BATCH,默認是SIMPLE,都在枚舉類ExecutorType裏面。

(2)TransactionIsolationLevel:事務隔離級別,都在枚舉類TransactionIsolationLevel中定義。

(3)autoCommit:是否自動提交,主要是事務提交的設置。

DefaultSqlSession是SqlSession的實現類,該類主要提供操作數據庫的方法給開發人員使用。

這裏總結一下上面的過程,總共三個步驟:

步驟一:讀取Ibatis的主配置文件,並將文件讀成文件流形式(InputStream)。

步驟二:從主配置文件流中讀取文件的各個節點信息並存放到Configuration對象中。讀取mappers節點的引用文件,並將這些文件的各個節點信息存放到Configuration對象。

步驟三:根據Configuration對象的信息獲取數據庫連接,並設置連接的事務隔離級別等信息,將經過包裝數據庫連接對象SqlSession接口返回,DefaultSqlSession是SqlSession的實現類,所以這裏返回的是DefaultSqlSession,SqlSession接口裏面就是對外提供的各種數據庫操作。

原理分析之四:一次SQL查詢的源碼分析

上回我們講到Mybatis加載相關的配置文件進行初始化,這回我們講一下一次SQL查詢怎麼進行的。

準備工作

Mybatis完成一次SQL查詢需要使用的代碼如下:

Java代碼

String resource = "mybatis.cfg.xml"; Reader reader = Resources.getResourceAsReader(resource); SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader); <strong> </strong>SqlSession session = ssf.openSession(); try { UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1"); System.out.println(user); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); }

本次我們需要進行深入跟蹤分析的是:

Java代碼

SqlSession session = ssf.openSession(); UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");

第一步:打開一個會話,我們看看裏面具體做了什麼事情。

Java代碼

SqlSession session = ssf.openSession();

DefaultSqlSessionFactory的 openSession()方法內容如下:

Java代碼

public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }

跟進去,我們看一下openSessionFromDataSource方法到底做了啥:

Java代碼

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Connection connection = null; try { final Environment environment = configuration.getEnvironment(); final DataSource dataSource = getDataSourceFromEnvironment(environment); TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); }connection = wrapConnection(connection); Transaction tx = transactionFactory.newTransaction(connection, autoCommit); Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch(Exception e) { closeConnection(connection); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset();} }

這裏我們分析一下這裏所涉及的步驟:

(1)獲取前面我們加載配置文件的環境信息,並且獲取環境信息中配置的數據源。

(2)通過數據源獲取一個連接,對連接進行包裝代理(通過JDK的代理來實現日誌功能)。

(3)設置連接的事務信息(是否自動提交、事務級別),從配置環境中獲取事務工廠,事務工廠獲取一個新的事務。

(4)傳入事務對象獲取一個新的執行器,並傳入執行器、配置信息等獲取一個執行會話對象。

從上面的代碼我們可以得出,一次配置加載只能有且對應一個數據源。對於上述步驟,我們不難理解,我們重點看看新建執行器和DefaultSqlSession。

首先,我們看看newExecutor到底做了什麼?

Java代碼

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; }

上面代碼的執行步驟如下:

(1)判斷執行器類型,如果配置文件中沒有配置執行器類型,則採用默認執行類型ExecutorType.SIMPLE。

(2)根據執行器類型返回不同類型的執行器(執行器有三種,分別是 BatchExecutor、SimpleExecutor和CachingExecutor,後面我們再詳細看看)。

(3)跟執行器綁定攔截器插件(這裏也是使用代理來實現)。

DefaultSqlSession到底是幹什麼的呢?

DefaultSqlSession實現了SqlSession接口,裏面有各種各樣的SQL執行方法,主要用於SQL操作的對外接口,它會的調用執行器來執行實際的SQL語句。

接下來我們看看SQL查詢是怎麼進行的

Java代碼

UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");

實際調用的是DefaultSqlSession類的selectOne方法,該方法代碼如下:

Java代碼

public Object selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List list = selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }

我們再看看selectList方法(實際上是調用該類的另一個selectList方法來實現的):

Java代碼

public List selectList(String statement, Object parameter) { return selectList(statement, parameter, RowBounds.DEFAULT); } public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

第二個selectList的執行步驟如下:

(1)根據SQL的ID到配置信息中找對應的MappedStatement,在之前配置被加載初始化的時候我們看到了系統會把配置文件中的SQL塊解析並放到一個MappedStatement裏面,並將MappedStatement對象放到一個Map裏面進行存放,Map的key值是該SQL塊的ID。

(2)調用執行器的query方法,傳入MappedStatement對象、SQL參數對象、範圍對象(此處爲空)和結果處理方式。

好了,目前只剩下一個疑問,那就是執行器到底怎麼執行SQL的呢?

上面我們知道了,默認情況下是採用SimpleExecutor執行的,我們看看這個類的doQuery方法:

Java代碼

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler); stmt = prepareStatement(handler); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }

doQuery方法的內部執行步驟:

(1) 獲取配置信息對象。

(2)通過配置對象獲取一個新的StatementHandler,該類主要用來處理一次SQL操作。

(3)預處理StatementHandler對象,得到Statement對象。

(4)傳入Statement和結果處理對象,通過StatementHandler的query方法來執行SQL,並對執行結果進行處理。

我們看一下newStatementHandler到底做了什麼?

Java代碼

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }

上面代碼的執行步驟:

(1)根據相關的參數獲取對應的StatementHandler對象。

(2)爲StatementHandler對象綁定攔截器插件。

RoutingStatementHandler類的構造方法RoutingStatementHandler如下:

Java代碼

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }

根據 MappedStatement對象的StatementType來創建不同的StatementHandler,這個跟前面執行器的方式類似。StatementType有STATEMENT、PREPARED和CALLABLE三種類型,跟JDBC裏面的Statement類型一一對應。

我們看一下prepareStatement方法具體內容:

Java代碼

private Statement prepareStatement(StatementHandler handler) throws SQLException {Statement stmt; Connection connection = transaction.getConnection(); //從連接中獲取Statement對象 stmt = handler.prepare(connection); //處理預編譯的傳入參數 handler.parameterize(stmt); return stmt; }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章