1. 程式人生 > >MyBatis原始碼分析-SQL語句執行的完整流程

MyBatis原始碼分析-SQL語句執行的完整流程

 MyBatis 是支援定製化 SQL、儲存過程以及高階對映的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或註解,將介面和 Java 的 POJOs(Plain Old Java Objects,普通的 Java物件)對映成資料庫中的記錄。如何新建MyBatis原始碼工程請點選MyBatis原始碼分析-IDEA新建MyBatis原始碼工程

  MyBatis框架主要完成的是以下2件事情:

  1. 根據JDBC規範建立與資料庫的連線。
  2. 通過反射打通Java物件與資料庫引數互動之間相互轉換的關係。

  MyBatis框架是一種典型的互動式框架,先準備好互動的必要條件,然後構建一個互動的環境,在互動環境中劃分會話,在會話中與資料庫進行互動資料。

1 MyBatis主要的類

  • Configuration        MyBatis所有的配置資訊都維持在Configuration物件之中。
  • SqlSession            作為MyBatis工作的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
  • Executor               MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
  • StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合。
  • ParameterHandler  負責對使用者傳遞的引數轉換成JDBC Statement 所需要的引數,
  • ResultSetHandler   負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合;
  • TypeHandler          負責java資料型別和jdbc資料型別之間的對映和轉換
  • MappedStatement  MappedStatement維護了一條<select|update|delete|insert>節點的封裝,
  • SqlSource              負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
  • BoundSql              表示動態生成的SQL語句以及相應的引數資訊

  以上幾個類在SQL操作中都會涉及,在SQL操作中重點關注下SQL引數什麼時候寫入和結果集怎麼轉換為Java物件,這兩個過程正好對應的類是PreparedStatementHandler和ResultSetHandler類。

2 SQL執行流程

  MyBatis主要設計目的還是為了讓我們在執行SQL時對輸入輸出的資料的管理更加方便,所以方便的讓我們寫出SQL和方便的獲取SQL的執行結果是MyBatis的核心競爭力。下面就用一個例子來從原始碼角度看一下SQL的完整執行流程。

新建配置檔案conf.xml:

 conf.xml

首先建立資料表,這裡就以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

再新建usre表的配置檔案:

 userMapper.xml

最後新建測試類:

複製程式碼
/**
 * 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;
}
複製程式碼

相關推薦

MyBatis原始碼分析-SQL語句執行完整流程

 MyBatis 是支援定製化 SQL、儲存過程以及高階對映的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或註解,將介面和 Java 的 POJOs(Plain

精盡MyBatis原始碼分析 - SQL執行過程(一)之 Executor

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址

精盡MyBatis原始碼分析 - SQL執行過程(二)之 StatementHandler

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址

精盡MyBatis原始碼分析 - SQL執行過程(三)之 ResultSetHandler

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址

精盡MyBatis原始碼分析 - SQL執行過程(四)之延遲載入

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址

mysql優化–explain分析sql語句執行效率

  Explain命令在解決資料庫效能上是第一推薦使用命令,大部分的效能問題可以通過此命令來簡單的解決,Explain可以用來檢視SQL語句的執行效 果,可以幫助選擇更好的索引和優化查詢語句,寫出更好的優化語句。 Explain語法:explain select … from …

Mysql explain分析sql語句執行效率

mysql優化–explain分析sql語句執行效率 Explain命令在解決資料庫效能上是第一推薦使用命令,大部分的效能問題可以通過此命令來簡單的解決,Explain可以用來檢視SQL語句的執行效 果,可以幫助選擇更好的索引和優化查詢語句,寫出更好的優化語句。 Explain語法:explain sel

Explain分析sql語句執行效率

使用Explain命令會有以下屬性輸出:        1》id:這是SELECT的查詢序列號        2》select_type:select_type就是select的型別:   &n

mysql優化(三)–explain分析sql語句執行效率

mushu 釋出於 11個月前 (06-04) 分類:Mysql 閱讀(651) 評論(0) Explain命令在解決資料庫效能上是第一推薦使用命令,大部分的效能問題可以通過此命令來簡單的解決,Explain可以用來檢視SQL語句的執行效 果,可以幫助選擇更好的索引和優化查詢語句,寫出

實現分析sql語句執行過程和編譯時間的方法

        有時候我們經常為我們的sql語句執行效率低下發愁,反覆優化後,可還是得不到提高。         那麼你就用這條語句找出你sql到底是在哪裡慢了         示例:         SET STATISTICS io ON        SET STATISTICS time ON   

精盡MyBatis原始碼分析 - SqlSession 會話與 SQL 執行入口

> 該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋([Mybatis原始碼分析 GitHub 地址](https://github.com/liu844869663/mybatis-3)、[Mybatis-Spring 原始碼分析 GitHub 地址

Mybatis原始碼分析(3)—— 從Mybatis的視角去看Bean的初始化流程

不涉及Spring完整的啟動流程,僅僅從Mybatis的視角去分析幾個關鍵的方法,找到Mybatis是如何通過這幾個擴充套件點植入進去的,反過來看Spring是如何設計,埋下這些伏筆,實現其可擴充套件性。 springContext-mybatis.xml的配置: <!--

MyBatis學習總結(六)---使用log4j2將sql語句執行記錄輸出控制檯和檔案中

  在上一篇部落格中我簡單的介紹了在MyBatis中如何使用日誌,並給出了一個在MyBatis中使用log4j的示例。    MyBatis中日誌的使用及使用log4j示例   下面介紹在MyBatis中如何使用log4j2將sql語句執行記錄輸出控制

標準sql語句執行分析

標準sql語句執行分析 SELECT count() as “人數” ,round(avg(reseau),2) as “平均工資” FROM ( select t.,serial,shopaddress ,rownum as rn FROM ( SELECT openid,account,

MyBatis原始碼分析---呼叫SqlSession增刪改查到底執行了什麼步驟

SqlSession建立流程: sqlSession可謂是MyBatis中最為核心的物件,相當於與資料庫的一次會話,是程式和資料庫的橋樑。 SqlSession物件由SqlSessionFactory工廠物件建立 SqlSessionFactory工廠物件提供了一大堆過

原始碼分析struts框架執行流程

struts 原始碼解析 ActionServlet 的執行流程 •Tomcat 及封裝配置 2 // web.xml 檔案的 標籤,配置則伺服器啟動則建立ActionServlet,否則訪問時建立 Tomcat 一啟動就將 web.xml 檔案讀取到記憶體,

springmvc 專案完整示例04 整合mybatis mybatis所需要的jar包 mybatis配置檔案 sql語句 mybatis應用

百度百科: MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了google code,並且改名為MyBatis 。2013年11月遷移到Github。 iBATIS一詞來源於“internet”和“abatis”

通過分析SQL語句執行計劃優化SQL(總結)

如何幹預執行計劃 - - 使用hints提示 基於代價的優化器是很聰明的,在絕大多數情況下它會選擇正確的優化器,減輕了DBA的負擔。但有時它也聰明反被聰明誤,選擇了很差的執行計劃,使某個語句的執行變得奇慢無比。此時就需要DBA進行人為的干預,告訴優化器使用我們指定的存取路徑或連線型別生成執行計劃,從而使

SprignMVC+myBatis整合+mybatis原始碼分析+動態代理實現流程+如何根據mapper介面生成其實現類

首先熟悉三個概念: SqlSessionFactoryBean –為整合應用提供SqlSession物件資源 MapperFactoryBean –根據指定的Mapper介面生成Bean例項 MapperScannerConfigurer –根據指定

mybatis ${}導致mysql資料庫按照SQL語句執行的和程式執行的結果不一致

今天遇到一個問題,某段執行SQL的程式在我這裡執行正常,但是到了到了我同事那裡就不正常。我倆連的不是同一個資料庫地址,但是資料庫的內容是一致的。後來把xml檔案裡面處理引數的${}換成#{}就正常了,雖然問題解決了但是原因還是不知道。