還是以第一篇博客中給出的例子,根據代碼實例來入手分析。
1 static { 2 InputStream inputStream = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-configuration.xml"); 3 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 4 } 5 6 /** 7 * 查詢單個記錄 8 */ 9 @Test 10 public void testSelectOne() { 11 SqlSession session = sqlSessionFactory.openSession(); 12 User user = session.selectOne(NAME_SPACE + ".selectUserById", 1); 13 System.out.println(user); 14 session.close(); 15 }
如何加載配置文件前面也已經介紹了,通過配置文件產生SqlSessionFactory,追溯源碼可以發現其實現是 DefaultSqlSessionFactory。
1 public SqlSessionFactory build(Configuration config) { 2 return new DefaultSqlSessionFactory(config); 3 }
得到 SqlSessionFactory 之后,就可以通過 SqlSessionFactory 去獲取 SqlSession 對象。源碼如下:
1 @Override 2 public SqlSession openSession() { 3 return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); 4 } 5 6 7 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 8 Transaction tx = null; 9 try { 10 //Environment對象封裝了配置文件中對於數據源和事務的配置 11 final Environment environment = configuration.getEnvironment(); 12 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); 13 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); 14 //獲取Executor對象,用來執行sql語句 15 final Executor executor = configuration.newExecutor(tx, execType); 16 return new DefaultSqlSession(configuration, executor, autoCommit); 17 } catch (Exception e) { 18 closeTransaction(tx); // may have fetched a connection so lets call close() 19 throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); 20 } finally { 21 ErrorContext.instance().reset(); 22 } 23 }
這里我們重點看一下第 15 行代碼:
1 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 2 executorType = executorType == null ? defaultExecutorType : executorType; 3 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; 4 Executor executor; 5 if (ExecutorType.BATCH == executorType) { 6 executor = new BatchExecutor(this, transaction); 7 } else if (ExecutorType.REUSE == executorType) { 8 executor = new ReuseExecutor(this, transaction); 9 } else { 10 executor = new SimpleExecutor(this, transaction); 11 } 12 if (cacheEnabled) { 13 executor = new CachingExecutor(executor); 14 } 15 executor = (Executor) interceptorChain.pluginAll(executor); 16 return executor; 17 }
根據執行器類型這里有多種不同的執行器Executor。
注意第 12 行代碼,如果我們開啟了緩存,即 cacheEnabled = true(這里是一級緩存,默認是開啟的),第13行代碼使用了裝飾器模式,在原有的 Executor 上裝飾了緩存功能。
第 15 行用於設置插件。
這時候已經得到SqlSession對象了,實際類型是 DefaultSqlSession。接下來我們就可以通過該對象來執行sql語句了。
1、insert 操作
1 /** 2 * 插入一條記錄 3 */ 4 @Test 5 public void testInsert() { 6 SqlSession session = sqlSessionFactory.openSession(); 7 User user = new User(2, "zhangsan", 22); 8 session.insert(NAME_SPACE + ".insertUser", user); 9 session.commit(); 10 session.close(); 11 }
通過第8行代碼,我們進入到 insert 方法:
1 @Override 2 public int insert(String statement, Object parameter) { 3 return update(statement, parameter); 4 }
注意:這里通過 insert 方法,調用的是 update 方法。
1 public int update(String statement, Object parameter) { 2 try { 3 dirty = true; 4 MappedStatement ms = configuration.getMappedStatement(statement); 5 return executor.update(ms, wrapCollection(parameter)); 6 } catch (Exception e) { 7 throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); 8 } finally { 9 ErrorContext.instance().reset(); 10 } 11 }
第4行根據給的statement參數,獲取配置的所有如下信息,並將其封裝到 MappedStatement 對象中,關於這個對象后面會詳細介紹。
1 <!-- 向 user 表插入一條數據 --> 2 <insert id="insertUser" parameterType="com.ys.po.User" > 3 insert into 4 user(<include refid="Base_Column_List" />) 5 value(#{id,jdbcType=INTEGER},#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER}) 6 </insert>
①、接着我們看第 5 行代碼,首先看 wrapCollection(parameter) 方法:
1 private Object wrapCollection(final Object object) { 2 if (object instanceof Collection) { 3 DefaultSqlSession.StrictMap<Object> map = new DefaultSqlSession.StrictMap<Object>(); 4 map.put("collection", object); 5 if (object instanceof List) { 6 map.put("list", object); 7 } 8 return map; 9 } else if (object != null && object.getClass().isArray()) { 10 DefaultSqlSession.StrictMap<Object> map = new DefaultSqlSession.StrictMap<Object>(); 11 map.put("array", object); 12 return map; 13 } 14 return object; 15 }
通過這段代碼的if-else if 語句主要做了如下兩個操作:
1、如果傳入的參數是集合 Collection,在 map 集合中放入一個key為"collection"、value為參數的鍵值對,接着判斷該集合是不是 List 類型,如果是,那么在 map 集合中在放入一個key為"list"、value為參數的鍵值對。
2、如果傳入的參數是數組類型,那么在 map 中放入一個key為"array"、value為參數的鍵值對。
注意:這里的 StrictMap ,其實就是一個 HashMap。

1 public static class StrictMap<V> extends HashMap<String, V> { 2 3 private static final long serialVersionUID = -5741767162221585340L; 4 5 @Override 6 public V get(Object key) { 7 if (!super.containsKey(key)) { 8 throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet()); 9 } 10 return super.get(key); 11 } 12 13 } 14 15 }
②、wrapCollection(parameter) 方法介紹完了。接着我們看 executor.update()方法:
這里需要說明的是 Executor 對象上面我們已經介紹了,由於默認是開啟一級緩存的,這時候我們進入 CachingExecutor 類的 update() 方法:
1 public int update(MappedStatement ms, Object parameterObject) throws SQLException { 2 flushCacheIfRequired(ms); 3 return delegate.update(ms, parameterObject); 4 }
首先我們看這里的第 2 行代碼:
private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { tcm.clear(cache); } }
這里表示的意思是是否清除緩存。看我們是否在配置文件中配置了 <cache> 標簽,以及我們是否在 <insert /> 標簽中是否增加了 flushCache="true"屬性。如果有其中任何一個,此次操作都會清除緩存。
接着我們再看第3行代碼,這里的delegate 是 Executor,但是這是一個接口,其真實類型是SimpleExecutor,經過裝飾器模式,調用 CachingExecutor 的 update 方法,經過處理后,最后最后調用 SimpleExecutor的update方法:
具體調用:
首先調用 BaseExecutor 的 update 方法
public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } clearLocalCache(); return doUpdate(ms, parameter); }
然后調用 doUpdate 方法,由於 SimpleExecutor 繼承 BaseExecutor 類,並重寫了 doUpdate 方法,我們看 SimpleExecutor 類的 doUpdate 方法:
1 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { 2 Statement stmt = null; 3 try { 4 Configuration configuration = ms.getConfiguration(); 5 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); 6 stmt = prepareStatement(handler, ms.getStatementLog()); 7 return handler.update(stmt); 8 } finally { 9 closeStatement(stmt); 10 } 11 }
看到這里,Statement 對象,看到我們熟悉的 JDBC 操作數據庫的對象了吧。我們直接看第 6 行代碼:
1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 2 Statement stmt; 3 Connection connection = getConnection(statementLog); 4 stmt = handler.prepare(connection, transaction.getTimeout()); 5 handler.parameterize(stmt); 6 return stmt; 7 }
第 3 行代碼獲取數據庫連接,是根據前面配置的數據源來獲取。接着我們看 handler.update(stemt) 方法:
1 public int update(Statement statement) throws SQLException { 2 String sql = boundSql.getSql(); 3 Object parameterObject = boundSql.getParameterObject(); 4 KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); 5 int rows; 6 if (keyGenerator instanceof Jdbc3KeyGenerator) { 7 statement.execute(sql, Statement.RETURN_GENERATED_KEYS); 8 rows = statement.getUpdateCount(); 9 keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); 10 } else if (keyGenerator instanceof SelectKeyGenerator) { 11 statement.execute(sql); 12 rows = statement.getUpdateCount(); 13 keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject); 14 } else { 15 statement.execute(sql); 16 rows = statement.getUpdateCount(); 17 } 18 return rows; 19 }
這里就都是我們熟悉的 JDBC 操作了。
2、update 和 delete 操作
1 /** 2 * 更新一條記錄 3 */ 4 @Test 5 public void testUpdate() { 6 SqlSession session = sqlSessionFactory.openSession(); 7 User user = new User(2, "lisi", 22); 8 session.update(NAME_SPACE + ".updateUserById", user); 9 session.commit(); 10 session.close(); 11 } 12 13 /** 14 * 刪除一條記錄 15 */ 16 @Test 17 public void testDelete() { 18 SqlSession session = sqlSessionFactory.openSession(); 19 session.delete(NAME_SPACE + ".deleteUserById", 2); 20 session.commit(); 21 session.close(); 22 }
進入到上述第 8 行和 第 19 行代碼,我們發現都是進入到和 上面 insert 操作一樣的代碼:
第 8 行:
public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
第 19 行:
@Override public int delete(String statement, Object parameter) { return update(statement, parameter); }
之后的 update 也是上面的代碼。這也和我們理解的應該保持一致。
結論:
insert、update、delete都是屬於對數據庫的行進行更新操作
所以這三種語句的執行都是采用的同種邏輯處理。最終都可以調用 executeUpdate() 方法來處理。唯一不同的是 select 操作,必須要調用 executeQuery() 來執行。
3、select 操作
1 /** 2 * 查詢單個記錄 3 */ 4 @Test 5 public void testSelectOne() { 6 SqlSession session = sqlSessionFactory.openSession(); 7 User user = session.selectOne(NAME_SPACE + ".selectUserById", 1); 8 System.out.println(user); 9 session.close(); 10 } 11 12 /** 13 * 查詢多個記錄 14 */ 15 @Test 16 public void testSelectList() { 17 SqlSession session = sqlSessionFactory.openSession(); 18 List<User> listUser = session.selectList(NAME_SPACE + ".selectUserAll"); 19 if (listUser != null) { 20 System.out.println(listUser.size()); 21 } 22 session.close(); 23 }
首先看第 7 行代碼:
1 public <T> T selectOne(String statement, Object parameter) { 2 // Popular vote was to return null on 0 results and throw exception on too many. 3 List<T> list = this.<T>selectList(statement, parameter); 4 if (list.size() == 1) { 5 return list.get(0); 6 } else if (list.size() > 1) { 7 throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); 8 } else { 9 return null; 10 } 11 }
看到上面的第 3 行代碼,我們可能馬上就明白了,其實selectOne() 和 selectList() 也都是調用的 selectList() 方法,只不過 selectOne() 是獲取集合的第一個元素而已。
接着看 selectList() 源碼:
1 @Override 2 public <E> List<E> selectList(String statement, Object parameter) { 3 return this.selectList(statement, parameter, RowBounds.DEFAULT); 4 } 5 6 @Override 7 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 8 try { 9 MappedStatement ms = configuration.getMappedStatement(statement); 10 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 11 } catch (Exception e) { 12 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); 13 } finally { 14 ErrorContext.instance().reset(); 15 } 16 }
看第10的 query 方法:
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 2 BoundSql boundSql = ms.getBoundSql(parameterObject); 3 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); 4 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 5 }
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2 throws SQLException { 3 Cache cache = ms.getCache(); 4 if (cache != null) { 5 flushCacheIfRequired(ms); 6 if (ms.isUseCache() && resultHandler == null) { 7 ensureNoOutParams(ms, parameterObject, boundSql); 8 @SuppressWarnings("unchecked") 9 List<E> list = (List<E>) tcm.getObject(cache, key); 10 if (list == null) { 11 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 12 tcm.putObject(cache, key, list); // issue #578 and #116 13 } 14 return list; 15 } 16 } 17 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 18 }
最后我們來到doQuery() 方法:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
1 @Override 2 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { 3 PreparedStatement ps = (PreparedStatement) statement; 4 ps.execute(); 5 return resultSetHandler.<E> handleResultSets(ps); 6 }
至此,select 操作也執行完畢了。