MyBatis 是支持定製化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 能夠對配置和原生Map使用簡單的 XML 或註解,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。如何新建MyBatis源碼工程請點擊MyBatis源碼分析-IDEA新建MyBatis源碼工程。html
MyBatis框架主要完成的是如下2件事情:java
MyBatis框架是一種典型的交互式框架,先準備好交互的必要條件,而後構建一個交互的環境,在交互環境中劃分會話,在會話中與數據庫進行交互數據。mysql
以上幾個類在SQL操做中都會涉及,在SQL操做中重點關注下SQL參數何時寫入和結果集怎麼轉換爲Java對象,這兩個過程正好對應的類是PreparedStatementHandler和ResultSetHandler類。git
(圖片來自《深刻理解mybatis原理》 MyBatis的架構設計以及實例分析)github
MyBatis主要設計目的仍是爲了讓咱們在執行SQL時對輸入輸出的數據的管理更加方便,因此方便的讓咱們寫出SQL和方便的獲取SQL的執行結果是MyBatis的核心競爭力。下面就用一個例子來從源碼角度看一下SQL的完整執行流程。sql
新建配置文件conf.xml:數據庫
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="false"/> <!--setting name="logImpl" value="STDOUT_LOGGING"/--> <!-- 日誌 --> </settings> <typeAliases> <typeAlias type="com.luoxn28.dao.User" alias="User"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <!-- 聲明使用那種事務管理機制 JDBC/MANAGED --> <!-- 配置數據庫鏈接信息 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.1.150:3306/xxx" /> <property name="username" value="xxx" /> <property name="password" value="xxx" /> </dataSource> </environment> </environments> <mappers> <mapper resource="userMapper.xml"/> </mappers> </configuration>
首先創建數據表,這裏就以user表爲例 :緩存
DROP TABLE IF EXISTS user; CREATE TABLE user ( id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32) NOT NULL, password VARCHAR(32) NOT NULL, sex int, email VARCHAR(32), phone VARCHAR(16), admin VARCHAR(16) );
而後新建與數據表對應的類User:安全
/** * User - 用戶類 */ public class User { public static final int MAN = 0; // 男生 public static final int WOMAN = 1; // 女生 public static final int OTHER = 2; // 其餘 private int id; // 用戶id private String name; // 用戶名 private String password; // 用戶密碼 private int sex; // 用戶性別 private String email; // 用戶郵箱 private String phone; // 用戶手機 private String admin; // 用戶是不是管理員,"admin"表示是管理員,其餘爲普通用戶 public User() { } public User(String name, String password, int sex, String email, String phone) { this.name = name; this.password = password; this.sex = sex; this.email = email; this.phone = phone; this.admin = ""; } public User(String name, String password, String sex, String email, String phone) { this.name = name; this.password = password; setSex(sex); // this.sex = sex; this.email = email; this.phone = phone; this.admin = ""; } 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 String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public void setSex(String sexStr) { int sex = Integer.valueOf(sexStr); switch (Integer.valueOf(sexStr)) { case 0: { this.sex = MAN; break; } case 1: { this.sex = WOMAN; break; } default: { this.sex = OTHER; break; } } } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getAdmin() { return admin; } public void setAdmin(String admin) { this.admin = admin; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", sex=" + sex + ", email='" + email + '\'' + ", phone='" + phone + '\'' + ", admin='" + admin + '\'' + '}'; } }
再新建usre表的配置文件:session
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.luoxn28.dao.UserDao"> <select id="getById" parameterType="int" resultType="User"> SELECT * FROM user WHERE id=#{id}; <!-- #{xxx} xxx爲類中的數據域名稱 --> </select> <select id="getAll" resultType="com.luoxn28.dao.User"> SELECT * FROM user; </select> </mapper>
最後新建測試類:
/** * MyBatis測試類 */ public class TestMain { public static void main(String[] args) throws IOException { String resouce = "conf.xml"; InputStream is = Resources.getResourceAsStream(resouce); // 構建sqlSession工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); // 獲取sqlSession SqlSession session = sqlSessionFactory.openSession(); User user; try { /** * 第一種方式: 直接執行已映射的 SQL 語句 */ String statement = "com.luoxn28.dao.UserDao.getById"; user = session.selectOne(statement, 1); System.out.println(user); } finally { session.close(); } /** * 第二種方式: 執行更清晰和類型安全的代碼 */ // UserDao userDao = session.getMapper(UserDao.class); // user = userDao.getById(1); // System.out.println(user); } }
因爲咱們分析的是SQL的執行流程,那就重點關注下 user = session.selectOne(statement, 1); 這行代碼~ 注意,傳進去的參數是1。
session是DefaultSqlSession類型的,由於sqlSessionFactory默認生成的SqlSession是DefaultSqlSession類型。selectOne()會調用selectList()。
// DefaultSqlSession類 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // CURD操做是交給Excetor去處理的 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(); } }
在DefaultSqlSession.selectList中的各類CURD操做都是通多Executor進行的,這裏executor的類型是CachingExecutor,接着跳轉到其中的query方法中。
// CachingExecutor 類 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 獲取綁定的sql命令,好比"SELECT * FROM xxx" CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
getBoundSql爲了獲取綁定的sql命令,在建立完cacheKey以後,就進入到CachingExecutor 類中的另外一個query方法中。
// CachingExecutor 類 @Override 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, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
這裏真正執行query操做的是SimplyExecutor代理來完成的,接着就進入到了SimplyExecutor的父類BaseExecutor的query方法中。
// SimplyExecutor的父類BaseExecutor類 @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; /** * localCache是一級緩存,若是找不到就調用queryFromDatabase從數據庫中查找 */ list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
由於是第一次SQL查詢操做,因此會調用queryFromDatabase方法來執行查詢。
// SimplyExecutor的父類BaseExecutor類 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
從數據庫中查詢數據,進入到SimplyExecutor中進行操做。
// SimplyExecutor類 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); // 子流程1:SQL查詢參數的設置 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler封裝了Statement // 子流程2:SQL查詢操做和結果集的封裝 return handler.<E>query(stmt); } finally { closeStatement(stmt); } }
注意,在prepareStatement方法中會進行SQL查詢參數的設置,也就是我們最開始傳遞進來的參數,其值爲1。handler.<E>query(stmt)方法中會進行實際的SQL查詢操做和結果集的封裝(封裝成Java對象)。當流程走到這裏時,程序已經壓棧有必定深度了,由於接下來程序分析會兵分兩路,一方面深刻到SQL查詢及結果集的設置子流程1中,而後再深刻到SQL查詢操做和結果集的封裝子流程2,由於還會回到這裏,因此就來一張調用棧的特寫吧:
子流程1:SQL查詢參數的設置
// SimplyExecutor類 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 獲取一個Connection Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); // 設置SQL查詢中的參數值 return stmt; }
經過getConnection方法來獲取一個Connection,調用prepare方法來獲取一個Statement(這裏的handler類型是RoutingStatementHandler,RoutingStatementHandler的prepare方法調用的是PrepareStatementHandler的prepare方法,由於PrepareStatementHandler並無覆蓋其父類的prepare方法,其實最後調用的是BaseStatementHandler中的prepare方法。是否是繞暈了,那就再看一遍吧 :) )。調用parameterize方法來設置SQL的參數值(這裏最後調用的是PrepareStatementHandler中的parameterize方法,而PrepareStatementHandler.parameterize方法調用的是DefaultParameterHandler中的setParameters方法)。
// PrepareStatementHandler類 @Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); }
// DefaultParameterHandler類 @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
到這裏爲止,已經給Statement設置了最初傳遞進去的參數(值爲1)了,那麼接着分析流程2:
流程2:SQL查詢及結果集的設置
// RoutingStatementHandler類 @Override public <E> List<E> query(Statement statement) throws SQLException { return delegate.<E>query(statement); }
// RoutingStatementHandler類 @Override public <E> List<E> query(Statement statement) throws SQLException { // 這裏就到了熟悉的PreparedStatement了 PreparedStatement ps = (PreparedStatement) statement; // 執行SQL查詢操做 ps.execute(); // 結果交給ResultHandler來處理 return resultSetHandler.<E> handleResultSets(ps); }
// DefaultResultSetHandler類(封裝返回值,將查詢結果封裝成Object對象) @Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); }
ResultSetWrapper是ResultSet的包裝類,調用getFirstResultSet方法獲取第一個ResultSet,同時獲取數據庫的MetaData數據,包括數據表列名、列的類型、類序號等,這些信息都存儲在ResultSetWrapper類中了。而後調用handleResultSet方法來來進行結果集的封裝。
// DefaultResultSetHandler類 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }
這裏調用handleRowValues方法來進行值的設置:
// DefaultResultSetHandler類 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 封裝數據 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
// DefaultResultSetHandler類 // 封裝數據 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>(); skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } }
// DefaultResultSetHandler類 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // createResultObject爲新建立的對象,數據表對應的類 Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty(); if (shouldApplyAutomaticMappings(resultMap, false)) { // 這裏把數據填充進去,metaObject中包含了resultObject信息 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; return resultObject; } return resultObject; }
// DefaultResultSetHandler類(把ResultSet中查詢結果填充到JavaBean中) private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (autoMapping.size() > 0) { // 這裏進行for循環調用,由於user表中總共有7項,因此也就調用7次 for (UnMappedColumnAutoMapping mapping : autoMapping) { // 這裏將esultSet中查詢結果轉換爲對應的實際類型 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value); } } } return foundValues; }
mapping.typeHandler.getResult會獲取查詢結果值的實際類型,好比咱們user表中id字段爲int類型,那麼它就對應Java中的Integer類型,而後經過調用statement.getInt("id")來獲取其int值,其類型爲Integer。metaObject.setValue方法會把獲取到的Integer值設置到Java類中的對應字段。
// MetaObject類 public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null && prop.getChildren() != null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value); } else { objectWrapper.set(prop, value); } }
metaValue.setValue方法最後會調用到Java類中對應數據域的set方法,這樣也就完成了SQL查詢結果集的Java類封裝過程。最後貼一張調用棧到達Java類的set方法中的快照:
參考:
二、《深刻分析Java Web技術內幕》的iBatis章節
三、《深刻理解mybatis原理》 MyBatis的架構設計以及實例分析
四、