欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Mybatis?MappedStatement類核心原理詳解

 更新時間:2022年11月18日 11:01:50   作者:氵奄不死的魚  
這篇文章主要介紹了Mybatis?MappedStatement類,mybatis的mapper文件最終會被解析器,解析成MappedStatement,其中insert|update|delete|select每一個標簽分別對應一個MappedStatement

MappedStatement

MappedStatement 類是 Mybatis 框架的核心類之一,它存儲了一個 sql 對應的所有信息

Mybatis 通過解析 XML 和 mapper 接口上的注解,生成 sql 對應的 MappedStatement 實例,并放入 SqlSessionTemplate 中 configuration 類屬性中

正真執(zhí)行 mapper 接口中的方法時,會從 configuration 中找到對應的 mappedStatement,然后進行后續(xù)的操作

MyBatis通過MappedStatement描述<select|update|insert|delete>或者@Select、@Update等注解配置的SQL信息。在介紹MappedStatement組件之前,我們先來了解一下MyBatis中SQL Mapper的配置。不同類型的SQL語句需要使用對應的XML標簽進行配置。這些標簽提供了很多屬性,用來控制每條SQL語句的執(zhí)行行為。下面是標簽中的所有屬性:

<select

id="getUserById"

parameterType="int"

parameterMap="deprecated"

resultType="hashmap"

resultMap="userResultMap"

flushCache="false"

useCache="true"

timeout="10000"

fetchSize="256"

statementType="PREPARED"

resultSetType="FORWARD ONLY">

public final class MappedStatement {
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

對應字段含義

id:在命名空間中唯一的標識符,可以被用來引用這條配置信息。

parameterType:用于指定這條語句的參數類的完全限定名或別名。這個屬性是可選的,MyBatis能夠根據Mapper接口方法中的參數類型推斷出傳入語句的類型。

parameterMap:引用通過標簽定義的參數映射,該屬性已經廢棄。

resultType:從這條語句中返回的期望類型的類的完全限定名或別名。注意,如果返回結果是集合類型,則resultType屬性應該指定集合中可以包含的類型,而不是集合本身。

resultMap:用于引用通過標簽配置的實體屬性與數據庫字段之間建立的結果集的映射(注意:resultMap和resultType屬性不能同時使用)。

flushCache:用于控制是否刷新緩存。如果將其設置為true,則任何時候只要語句被調用,都會導致本地緩存和二級緩存被清空,默認值為false。

useCache:是否使用二級緩存。如果將其設置為true,則會導致本條語句的結果被緩存在MyBatis的二級緩存中,對應標簽,該屬性的默認值為true。

timeout:驅動程序等待數據庫返回請求結果的秒數,超時將會拋出異常。

fetchSize:用于設置JDBC中Statement對象的fetchSize屬性,該屬性用于指定SQL執(zhí)行后返回的最大行數。

statementType:參數可選值為STATEMENT、PREPARED或CALLABLE,這會讓MyBatis分別使用Statement、PreparedStatement或CallableStatement與數據庫交互,默認值為PREPARED。

resultSetType:參數可選值為FORWARD_ONLY、SCROLL_SENSITIVE或SCROLL_INSENSITIVE,用于設置ResultSet對象的特征,具體可參考第2章JDBC規(guī)范的相關內容。默認未設置,由JDBC驅動決定。

databaseId:如果配置了databaseIdProvider,MyBatis會加載所有不帶databaseId或匹配當前databaseId的語句。

resultOrdered:這個設置僅針對嵌套結果select語句適用,如果為true,就是假定嵌套結果包含在一起或分組在一起,這樣的話,當返回一個主結果行的時候,就不會發(fā)生對前面結果集引用的情況。這就使得在獲取嵌套結果集的時候不至于導致內存不夠用,默認值為false。

resultSets:這個設置僅對多結果集的情況適用,它將列出語句執(zhí)行后返回的結果集并每個結果集給一個名稱,名稱使用逗號分隔。

lang:該屬性用于指定LanguageDriver實現,MyBatis中的LanguageDriver用于解析<select|update|insert|delete>標簽中的SQL語句,生成SqlSource對象。

介紹jdbc的幾個相關屬性

resultSetType

在創(chuàng)建PreparedStatement時,resultSetType參數設置的是TYPE_SCROLL_INSENSITIVE或TYPE_SCROLL_SENSITIVE,

這兩個參數的共同特點是允許結果集(ResultSet)的游標可以上下移動。而默認的TYPE_FORWARD_ONLY參數只允許結果集的游標向下移動。

如果PreparedStatement對象初始化時resultSetType參數設置為TYPE_FORWARD_ONLY,在從ResultSet(結果集)中讀取記錄的時,對于訪問過的記錄就自動釋放了內存。而設置為TYPE_SCROLL_INSENSITIVE或TYPE_SCROLL_SENSITIVE時為了保證能游標能向上移動到任意位置,已經訪問過的所有都保留在內存中不能釋放。所以大量數據加載的時候,就OOM了。

statement = conn.prepareStatement(querySql,ResultSet.TYPE_FORWARD_ONLY,
                        ResultSet.CONCUR_READ_ONLY);

resultSetType是設置ResultSet對象的類型標示可滾動,或者是不可滾動。取值如下:

默認只能向前滾動。

fetchSize

默認情況下pgjdbc driver會一次性拉取所有結果集,也就是在executeQuery的時候。對于大數據量的查詢來說,非常容易造成OOM。這種場景就需要設置fetchSize,執(zhí)行query的時候先返回第一批數據,之后next完一批數據之后再去拉取下一批。

場景與方案

場景:java端從數據庫讀取100W數據進行后臺業(yè)務處理。

常規(guī)實現1:分頁讀取出來。缺點:需要排序后分頁讀取,性能低下。

常規(guī)實現2:一次性讀取出來。缺點:需要很大內存,一般計算機不行。

非常規(guī)實現:建立長連接,利用服務端游標,一條一條流式返回給java端。

非常規(guī)實現優(yōu)化:jdbc中有個重要的參數fetchSize(它對業(yè)務實現無影響,即不會限制讀取條數等),優(yōu)化后可顯著提升性能。

public static void getAll(int fetchSize) {
        try {
            long beginTime=System.currentTimeMillis();
            Connection connection = DriverManager.getConnection(MYSQL_URL);
            connection.setAutoCommit(false); //為了設置fetchSize,必須設置為false
            String sql = "select * from test";
            PreparedStatement psst = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
            psst.setFetchSize(fetchSize);
            ResultSet rs = psst.executeQuery();
            int totalCount=0;
            while (rs.next()) {
                    totalCount++;
            }
            rs.close();
            psst.close();
            connection.close();
            long endTime=System.currentTimeMillis();
            System.out.println("totalCount:"+totalCount+";fetchSize:"+fetchSize+";耗時:"+(endTime-beginTime)+"ms");
        } catch (SQLException e) {
            e.printStackTrace();
        } 
    }

原理分析

1、先在服務端執(zhí)行查詢后將數據緩存在服務端。(耗時相對較長)

2、java端獲取數據時,利用服務端游標進行指針跳動,如果fetchSize為1000,則一次性跳動1000條,返回給java端緩存起來。(耗時較短,跳動次數為N/1000)

3、在調用next函數時,優(yōu)先從緩存中取數,其次執(zhí)行2過程。(內存讀取,耗時可忽略)

MappedStatement是怎么來的

還是以XML配置方式為例進行分析,簡單說下源碼查找的過程。Mapper對應的SQL語句定義在xml文件中,順著源碼會發(fā)現完成xml解析工作的是XMLMapperBuilder,其中對xml中“select|insert|update|delete”類型元素的解析方法為buildStatementFromContext;buildStatementFromContext使用了XMLStatementBuilder類對statement進行解析,并最終創(chuàng)建了MappedStatement。

所以,XMLStatementBuilder#parseStatementNode方法就是我們分析的重點。但是,在此之前需要有一點準備工作要做。由于MappedStatement最終是由MapperBuilderAssistant構建的,它其中存儲了一些Mapper級別的共享信息并應用到MappedStatement中。所以,先來簡單了解下它的由來:

  public class XMLMapperBuilder extends BaseBuilder {
  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  //省略部分字段和方法
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }
  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }
  //省略部分字段和方法
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //省略部分代碼
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  } 

好了,下面正是開始XMLStatementBuilder#parseStatementNode的分析了。為了節(jié)省篇幅,我直接通過代碼注釋的方式進行說明了,部分我認為不關鍵或不常用的內容沒有多說。

 /**
     * parseStatementNode方法是對select、insert、update、delete這四類元素進行解析,大體分為三個過程:
     * 1、解析節(jié)點屬性:如我們最常用的id、resultMap等;
     * 2、解析節(jié)點內的sql語句:首先把sql語句中包含的<include></include>等標簽轉為實際的sql語句,然后執(zhí)行靜態(tài)或動態(tài)節(jié)點處理;
     * 3、根據以上解析到的內容,使用builderAssistant創(chuàng)建MappedStatement,并加入Configuration中。
     * <p>
     * 以上過程中最關鍵的是第二步,它會根據實際使用的標簽,把sql片段轉為不同的SqlNode,以鏈表方式存儲到SqlSource中。
     */
    public void parseStatementNode() {
        //獲取標簽的id屬性,如selectById,對應Mapper接口中的方法名稱
        String id = context.getStringAttribute("id");
        //獲取databaseId屬性,我們一般都沒有寫。
        String databaseId = context.getStringAttribute("databaseId");
        /**
         * 這段代碼雖然不起眼,但是一定要進去看一下:其內部完成了對id的再次賦值,
         * 處理的方式是:id=namespace+"."+id,也就是當前Mapper的完全限定名+"."+id,
         * 比如我們之前例子中的com.raysonxin.dao.CompanyDao.selectById
         * 這也是Mapper接口中不能存在重載方法的根本原因。
         * */
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            return;
        }
        /**
         * 下面這塊代碼會依次獲取fetchSize、timeout、resultMap等屬性,
         * 需要注意的是,有些屬性雖然我們沒有設置,但是mybatis會設置默認值,
         * 具體可以查看mybatis的官方說明。
         */
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
        //默認值:XMLLanguageDriver
        LanguageDriver langDriver = getLanguageDriver(lang);
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
        //默認值:PREPARED
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        //默認值:DEFAULT
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
        // Include Fragments before parsing
        /**
         * 英文注釋也說了,在sql解析前處理 include 標簽,比如說,我們include了BaseColumns,
         * 它會把這個include標簽替換為BaseColumns內的sql內容
         * */
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
        // Parse selectKey after includes and remove them.
        //處理selectKey,主要針對不同的數據庫引擎做處理
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        /**
         * 到了關鍵步驟了:就是通過這句代碼完成了從xml標簽到SqlSource的轉換,
         * SqlSource是一個接口,這里返回的可能是DynamicSqlSource、也可能是RawSqlSource,
         * 取決于xml標簽中是否包含動態(tài)元素,比如 <if test=""></if>
         * */
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        String resultSets = context.getStringAttribute("resultSets");
        //下面這些是針對selectKey、KeyGenerator等進行處理,暫時跳過了。
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        KeyGenerator keyGenerator;
        String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
        if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                    configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                    ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
        /**
         * 節(jié)點及屬性都解析完成了,使用builderAssistant創(chuàng)建MappedStatement,
         * 并保存到Configuration#mappedStatements。
         * */
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
                fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
                resultSetTypeEnum, flushCache, useCache, resultOrdered,
                keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }

我們在xml中定義的select等語句就是通過這個parseStatementNode方法解析為MappedStatement的,整體來看比較容易理解,核心就是SqlSource的創(chuàng)建過程,

SqlSource是什么

SqlSource是整個MappedStatement的核心,MappedStatement其他一大堆字段都是為了準確的執(zhí)行它而定義的。SqlSource是個半成品的sql語句,因為對于其中的動態(tài)標簽還沒靜態(tài)化,其中的參數也未賦值。正是如此,才為我們后續(xù)的調用執(zhí)行提供了基礎,接下來重點看看SqlSource的構建過程。為了先從整體上了解,我畫了一個時序圖來描述SqlSource的解析、創(chuàng)建過程。

sqlsource是mapped statement對象中的一個屬性, 是一對一關系, 在創(chuàng)建mapped statement對象時創(chuàng)建,

sqlsource的主要作用就是創(chuàng)建一個sql語句

SqlSource主要有四種實現類, 其主要作用的就是以下三種 :

RawSqlSource : 存儲的是只有“#{}”或者沒有標簽的純文本sql信息

DynamicSqlSource : 存儲的是寫有“${}”或者具有動態(tài)sql標簽的sql信息

StaticSqlSource : 是DynamicSqlSource和RawSqlSource解析為BoundSql的一個中間環(huán)節(jié)

BoundSql

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;

oundSql類的作用就是生成我們最終執(zhí)行的sql語句, 里面包含一些屬性

  • sql : 執(zhí)行的sql語句 (包含 ?)
  • parameterMappings : 映射關系
  • parameterObject : 參數值

DynamicSqlSource

public class DynamicSqlSource implements SqlSource {
  private final Configuration configuration;
  private final SqlNode rootSqlNode;
  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}
  • field

configuration : 上下文, 里面包含了各種參數, 方便使用

  • method

getBoundSql :

構建DynamicContext對象, 里邊包含一個StringJoiner屬性, 遍歷sqlNode節(jié)點是用來拼接sql

將拼接的sql傳入, 將#{}替換成? , 并創(chuàng)建一個StaticSqlSource對象

通過StaticSqlSource對象獲取BoundSql對象

RawSqlSource

public class RawSqlSource implements SqlSource {
  private final SqlSource sqlSource;
  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }
}
  • field

sqlSource : 此屬性的類型為StaticSqlSource

  • method

getBoundSql : 獲取BoundSql對象

StaticSqlSource

只包含一個創(chuàng)建BoundSql對象的方法

public class StaticSqlSource implements SqlSource {
  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;
  public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
  }
  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }
  

到此這篇關于Mybatis MappedStatement類核心原理詳解的文章就介紹到這了,更多相關Mybatis MappedStatement內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringBoot整合canal實現數據緩存一致性解決方案

    SpringBoot整合canal實現數據緩存一致性解決方案

    canal主要用途是基于?MySQL?數據庫增量日志解析,提供增量數據訂閱和消費,canal是借助于MySQL主從復制原理實現,本文將給大家介紹SpringBoot整合canal實現數據緩存一致性解決方案,需要的朋友可以參考下
    2024-03-03
  • SpringBoot中MockMVC單元測試的實現

    SpringBoot中MockMVC單元測試的實現

    Mock是一種用于模擬和替換類的對象的方法,以便在單元測試中獨立于外部資源進行測試,本文主要介紹了SpringBoot中MockMVC單元測試的實現,具有應該的參考價值,感興趣的可以了解一下
    2024-02-02
  • Java設計模式七大原則之里氏替換原則詳解

    Java設計模式七大原則之里氏替換原則詳解

    在面向對象的程序設計中,里氏替換原則(Liskov Substitution principle)是對子類型的特別定義。本文將為大家詳細介紹Java設計模式七大原則之一的里氏替換原則,需要的可以參考一下
    2022-02-02
  • Java  隊列實現原理及簡單實現代碼

    Java 隊列實現原理及簡單實現代碼

    這篇文章主要介紹了Java 隊列實現原理及簡單實現代碼的相關資料,需要的朋友可以參考下
    2016-10-10
  • FluentMybatis實現mybatis動態(tài)sql拼裝和fluent api語法

    FluentMybatis實現mybatis動態(tài)sql拼裝和fluent api語法

    本文主要介紹了FluentMybatis實現mybatis動態(tài)sql拼裝和fluent api語法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • idea安裝漢化插件的圖文教程

    idea安裝漢化插件的圖文教程

    本文主要介紹了idea安裝漢化插件的圖文教程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-07-07
  • Java設計模式之觀察者模式

    Java設計模式之觀察者模式

    這篇文章主要介紹了Java設計模式之觀察者模式,觀察者模式,是一種行為性模型,又叫發(fā)布-訂閱模式,他定義對象之間一種一對多的依賴關系,使得當一個對象改變狀態(tài),則所有依賴于它的對象都會得到通知并自動更新,需要的朋友可以參考下
    2023-11-11
  • java加載properties文件的六種方法總結

    java加載properties文件的六種方法總結

    這篇文章主要介紹了java加載properties文件的六種方法總結的相關資料,需要的朋友可以參考下
    2017-05-05
  • SpringBoot 使用 OpenAPI3 規(guī)范整合 knife4j的詳細過程

    SpringBoot 使用 OpenAPI3 規(guī)范整合 knife4j的詳細過程

    Swagger工具集使用OpenAPI規(guī)范,可以生成、展示和測試基于OpenAPI規(guī)范的API文檔,并提供了生成客戶端代碼的功能,本文給大家介紹SpringBoot使用OpenAPI3規(guī)范整合knife4j的詳細過程,感興趣的朋友跟隨小編一起看看吧
    2023-12-12
  • 使用MyBatis攔截器實現SQL的完整打印

    使用MyBatis攔截器實現SQL的完整打印

    當我們使用Mybatis結合Mybatis-plus進行開發(fā)時,為了查看執(zhí)行sql的信息通常我們可以通過屬性配置的方式打印出執(zhí)行的sql語句,但這樣的打印出了sql語句常帶有占位符信息,不利于排錯,所以本文介紹了構建MyBatis攔截器,實現SQL的完整打印,需要的朋友可以參考下
    2024-07-07

最新評論