最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解MyBatis的動態(tài)SQL實現原理

 更新時間:2023年07月02日 16:50:39   作者:半夏之沫  
MyBatis提供了強大的動態(tài)SQL語句生成功能,以應對復雜的業(yè)務場景,本篇文章將結合MyBatis解析SQL語句的過程對MyBatis中對<if>,<where>,<foreach>等動態(tài)SQL標簽的支持進行分析,需要的朋友可以參考下

前言

MyBatis版本:3.5.6

正文

一. XML文檔中的節(jié)點概念

在分析MyBatis如何支持SQL語句之前,本小節(jié)先分析XML文檔中的節(jié)點概念。XML文檔中的每個成分都是一個節(jié)點,DOMXML節(jié)點的規(guī)定如下所示。

  • 整個文檔是一個文檔節(jié)點;
  • 每個XML標簽是一個元素節(jié)點;
  • 包含在元素節(jié)點中的文本是文本節(jié)點。

以一個XML文檔進行說明,如下所示。

<provinces>
    <province name="四川">
        <capital>成都</capital>
    </province>
    <province name="湖北">
        <capital>武漢</capital>
    </province>
</provinces>

如上所示,整個XML文檔是一個文檔節(jié)點,這個文檔節(jié)點有一個子節(jié)點,就是<provinces>元素節(jié)點,<provinces>元素節(jié)點有五個子節(jié)點,分別是:

  • 文本節(jié)點;
  • <province>元素節(jié)點;
  • 文本節(jié)點,
  • <province>元素節(jié)點;
  • 文本節(jié)點。

注意,在<provinces>元素節(jié)點的子節(jié)點中的文本節(jié)點的文本值均是\n,表示換行符。

同樣,<province>元素節(jié)點有三個子節(jié)點,分別是:

  • 文本節(jié)點;
  • <capital>元素節(jié)點;
  • 文本節(jié)點。

這里的文本節(jié)點的文本值也是\n

然后<capital>元素節(jié)點只有一個子節(jié)點,為一個文本節(jié)點。節(jié)點的子節(jié)點之間互為兄弟節(jié)點,例如<provinces>元素的五個子節(jié)點之間互為兄弟節(jié)點,name為"四川"的<province>元素節(jié)點的上一個兄弟節(jié)點為文本節(jié)點,下一個兄弟節(jié)點也為文本節(jié)點。

二. 動態(tài)SQL解析流程說明

整體的一個解析流程如下所示。

也就是寫在映射文件中的一條SQL,會最終被解析為DynamicSqlSource或者RawSqlSource,前者表示動態(tài)SQL,后者表示靜態(tài)SQL。

上圖中的MixedSqlNode,其通常的包含關系可以由下圖定義。

也就是映射文件中定義一條SQL語句的CRUD標簽里的各種子元素,均會被解析為一個SqlNode,比如包含了${}的文本,會被解析為TextSqlNode,不包含${}的文本,會被解析為StaticTextSqlNode,<choose>標簽會被解析為ChooseSqlNode等,同時又因為<choose>標簽中會再有<when>和<otherwise>子標簽,所以ChooseSqlNode中又會持有這些子標簽的SqlNode。

所以一條SQL最終就是由這條SQL對應的CRUD標簽解析成的各種SqlNode組合而成。

三. MyBatis解析動態(tài)SQL源碼分析

詳解MyBatis加載映射文件和動態(tài)代理的實現中已經知道,在XMLStatementBuilderparseStatementNode() 方法中,會解析映射文件中的<select>,<insert>,<update>和<delete>標簽(后續(xù)統(tǒng)一稱為CURD標簽),并生成MappedStatement然后緩存到Configuration中。

CURD標簽的解析由XMLLanguageDriver完成,每個標簽解析之后會生成一個SqlSource,可以理解為SQL語句,本小節(jié)將對XMLLanguageDriver如何完成CURD標簽的解析進行討論。

XMLLanguageDriver創(chuàng)建SqlSourcecreateSqlSource() 方法如下所示。

public SqlSource createSqlSource(Configuration configuration, 
        XNode script, Class<?> parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(
            configuration, script, parameterType);
    return builder.parseScriptNode();
}

如上所示,createSqlSource() 方法的入參中,XNode就是CURD標簽對應的節(jié)點,在createSqlSource() 方法中先是創(chuàng)建了一個XMLScriptBuilder,然后通過XMLScriptBuilder來生成SqlSource。先看一下XMLScriptBuilder的構造方法,如下所示。

public XMLScriptBuilder(Configuration configuration, XNode context, 
                    Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
}

XMLScriptBuilder的構造方法中,主要是將CURD標簽對應的節(jié)點緩存起來,然后初始化nodeHandlerMap,nodeHandlerMap中存放著處理MyBatis提供的支持動態(tài)SQL的標簽的處理器,initNodeHandlerMap() 方法如下所示。

private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
}

現在分析XMLScriptBuilderparseScriptNode() 方法,該方法會創(chuàng)建SqlSource,如下所示。

public SqlSource parseScriptNode() {
    // 解析動態(tài)標簽
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
        // 創(chuàng)建DynamicSqlSource并返回
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
        // 創(chuàng)建RawSqlSource并返回
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
}

XMLScriptBuilderparseScriptNode() 方法中,會根據XMLScriptBuilder中的isDynamic屬性判斷是創(chuàng)建DynamicSqlSource還是RawSqlSource,在這里暫時不分析DynamicSqlSourceRawSqlSource的區(qū)別,但是可以推測在parseDynamicTags() 方法中會改變isDynamic屬性的值,即在parseDynamicTags() 方法中會根據CURD標簽的節(jié)點生成一個MixedSqlNode,同時還會改變isDynamic屬性的值以指示當前CURD標簽中的SQL語句是否是動態(tài)的。

MixedSqlNode是什么,isDynamic屬性值在什么情況下會變?yōu)?strong>true,帶著這些疑問,繼續(xù)看parseDynamicTags() 方法,如下所示。

protected MixedSqlNode parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<>();
    // 獲取節(jié)點的子節(jié)點
    NodeList children = node.getNode().getChildNodes();
    // 遍歷所有子節(jié)點
    for (int i = 0; i < children.getLength(); i++) {
        XNode child = node.newXNode(children.item(i));
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE 
                    || child.getNode().getNodeType() == Node.TEXT_NODE) {
            // 子節(jié)點為文本節(jié)點
            String data = child.getStringBody("");
            // 基于文本節(jié)點的值并創(chuàng)建TextSqlNode
            TextSqlNode textSqlNode = new TextSqlNode(data);
            // isDynamic()方法可以判斷文本節(jié)點值是否有${}占位符
            if (textSqlNode.isDynamic()) {
                // 文本節(jié)點值有${}占位符
                // 添加TextSqlNode到集合中
                contents.add(textSqlNode);
                // 設置isDynamic為true
                isDynamic = true;
            } else {
                // 文本節(jié)點值沒有占位符
                // 創(chuàng)建StaticTextSqlNode并添加到集合中
                contents.add(new StaticTextSqlNode(data));
            }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {
            // 子節(jié)點為元素節(jié)點
            // CURD節(jié)點的子節(jié)點中的元素節(jié)點只可能為<if>,<foreach>等動態(tài)Sql標簽節(jié)點
            String nodeName = child.getNode().getNodeName();
            // 根據動態(tài)Sql標簽節(jié)點的名稱獲取對應的處理器
            NodeHandler handler = nodeHandlerMap.get(nodeName);
            if (handler == null) {
                throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
            }
            // 處理動態(tài)Sql標簽節(jié)點
            handler.handleNode(child, contents);
            // 設置isDynamic為true
            isDynamic = true;
        }
    }
    // 創(chuàng)建MixedSqlNode
    return new MixedSqlNode(contents);
}

按照正常執(zhí)行流程調用parseDynamicTags() 時,入參是CURD標簽節(jié)點,此時會遍歷CURD標簽節(jié)點的所有子節(jié)點,基于每個子節(jié)點都會創(chuàng)建一個SqlNode然后添加到SqlNode集合contents中,最后將contents作為入參創(chuàng)建MixedSqlNode并返回。

SqlNode是一個接口,在parseDynamicTags() 方法中,可以知道,TextSqlNode實現了SqlNode接口,StaticTextSqlNode實現了SqlNode接口,所以當節(jié)點的子節(jié)點是文本節(jié)點時,如果文本值包含有${}占位符,則創(chuàng)建TextSqlNode添加到contents中并設置isDynamictrue,如果文本值不包含${}占位符,則創(chuàng)建StaticTextSqlNode并添加到contents中。

如果CURD標簽節(jié)點的子節(jié)點是元素節(jié)點,由于CURD標簽節(jié)點的元素節(jié)點只可能為<if>,<foreach>等動態(tài)SQL標簽節(jié)點,所以直接會設置isDynamictrue,同時還會調用動態(tài)SQL標簽節(jié)點對應的處理器來生成SqlNode并添加到contents中。這里以<if>標簽節(jié)點對應的處理器的handleNode() 方法為例進行說明,如下所示。

public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    // 遞歸調用parseDynamicTags()解析<if>標簽節(jié)點
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    String test = nodeToHandle.getStringAttribute("test");
    // 創(chuàng)建IfSqlNode
    IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
    // 將IfSqlNode添加到contents中
    targetContents.add(ifSqlNode);
}

在<if>標簽節(jié)點對應的處理器的handleNode() 方法中,遞歸的調用了parseDynamicTags() 方法來解析<if>標簽節(jié)點,例如<where>,<foreach>等標簽節(jié)點對應的處理器的handleNode() 方法中也會遞歸調用parseDynamicTags() 方法,這是因為這些動態(tài)SQL標簽是可以嵌套使用的,比如<where>標簽節(jié)點的子節(jié)點可以為<if>標簽節(jié)點。通過上面的handleNode() 方法,大致可以知道MixedSqlNodeIfSqlNode也實現了SqlNode接口,下面看一下MixedSqlNodeIfSqlNode的實現,如下所示。

public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;
    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }
    @Override
    public boolean apply(DynamicContext context) {
        contents.forEach(node -> node.apply(context));
        return true;
    }
}
public class IfSqlNode implements SqlNode {
    private final ExpressionEvaluator evaluator;
    private final String test;
    private final SqlNode contents;
    public IfSqlNode(SqlNode contents, String test) {
        this.test = test;
        this.contents = contents;
        this.evaluator = new ExpressionEvaluator();
    }
    @Override
    public boolean apply(DynamicContext context) {
        if (evaluator.evaluateBoolean(test, context.getBindings())) {
            contents.apply(context);
            return true;
        }
        return false;
    }
}

其實到這里已經逐漸清晰明了了,按照正常執(zhí)行流程調用parseDynamicTags() 方法時,是為了將CURD標簽節(jié)點的所有子節(jié)點根據子節(jié)點類型生成不同的SqlNode并放在MixedSqlNode中,然后將MixedSqlNode返回,但是CURD標簽節(jié)點的子節(jié)點中如果存在動態(tài)SQL標簽節(jié)點,因為這些動態(tài)SQL標簽節(jié)點也會有子節(jié)點,所以此時會遞歸的調用parseDynamicTags() 方法,以解析動態(tài)SQL標簽節(jié)點的子節(jié)點,同樣會將這些子節(jié)點生成SqlNode并放在MixedSqlNode中然后將MixedSqlNode返回,遞歸調用parseDynamicTags() 方法時得到的MixedSqlNode會保存在動態(tài)SQL標簽節(jié)點對應的SqlNode中,比如IfSqlNode中就會將遞歸調用parseDynamicTags() 生成的MixedSqlNode賦值給IfSqlNodecontents字段。

不同的SqlNode都是可以包含彼此的,這是組合設計模式的應用,SqlNode之間的關系如下所示。

SqlNode接口定義了一個方法,如下所示。

public interface SqlNode {
    boolean apply(DynamicContext context);
}

每個SqlNodeapply() 方法中,除了實現自己本身的邏輯外,還會調用自己所持有的所有SqlNodeapply() 方法,最終逐層調用下去,所有SqlNodeapply() 方法均會被執(zhí)行。

四. DynamicSqlSource和RawSqlSource源碼分析

回到XMLScriptBuilderparseScriptNode() 方法,該方法中會調用parseDynamicTags() 方法以解析CURD標簽節(jié)點并得到MixedSqlNodeMixedSqlNode中含有被解析的CURD標簽節(jié)點的所有子節(jié)點對應的SqlNode,最后會基于MixedSqlNode創(chuàng)建DynamicSqlSource或者RawSqlSource,如果CURD標簽中含有動態(tài)SQL標簽或者SQL語句中含有${}占位符,則創(chuàng)建DynamicSqlSource,否則創(chuàng)建RawSqlSource。下面分別對DynamicSqlSourceRawSqlSource的實現進行分析。

1. DynamicSqlSource源碼分析

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);
        // 調用SqlNode的apply()方法完成Sql語句的生成
        rootSqlNode.apply(context);
        // SqlSourceBuilder可以將Sql語句中的#{}占位符替換為?
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        // 將Sql語句中的#{}占位符替換為?,并生成一個StaticSqlSource
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        // StaticSqlSource中保存有動態(tài)生成好的Sql語句,并且#{}占位符全部替換成了?
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        // 生成有序參數映射列表
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
    }
}

DynamicSqlSource的構造函數只是進行了簡單的賦值操作,重點在于其getBoundSql() 方法,在getBoundSql() 方法中,先是調用DynamicSqlSource中的SqlNodeapply() 方法以完成動態(tài)SQL語句的生成,此時生成的SQL語句中的占位符(如果有的話)為#{},然后再調用SqlSourceBuilderparse() 方法將SQL語句中的占位符從#{}替換為?并基于替換占位符后的SQL語句生成一個StaticSqlSource并返回,這里可以看一下StaticSqlSource的實現,如下所示。

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) {
        // 基于Sql語句創(chuàng)建一個BoundSql并返回
        return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }
}

所以分析到這里,可以知道DynamicSqlSourcegetBoundSql() 方法實際上會完成動態(tài)SQL語句的生成和#{}占位符替換,然后基于生成好的SQL語句創(chuàng)建BoundSql并返回。BoundSql對象的類圖如下所示。

實際上,MyBatis中執(zhí)行SQL語句時,如果映射文件中的SQL使用到了動態(tài)SQL標簽,那么MyBatis中的Executor(執(zhí)行器,后續(xù)文章中會進行介紹)會調用MappedStatementgetBoundSql() 方法,然后在MappedStatementgetBoundSql() 方法中又會調用DynamicSqlSourcegetBoundSql() 方法,所以MyBatis中的動態(tài)SQL語句會在這條語句實際要執(zhí)行時才會生成。

2. RawSqlSource源碼分析

現在看一下RawSqlSource的實現,如下所示。

public class RawSqlSource implements SqlSource {
    private final SqlSource sqlSource;
    public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
        // 先調用getSql()方法獲取Sql語句
        // 然后再執(zhí)行構造函數
        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;
        // 將Sql語句中的#{}占位符替換為?,生成一個StaticSqlSource并賦值給sqlSource
        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) {
        // 實際是調用StaticSqlSource的getBoundSql()方法
        return sqlSource.getBoundSql(parameterObject);
    }
}

RawSqlSource會在構造函數中就將SQL語句生成好并替換#{}占位符,在SQL語句實際要執(zhí)行時,就直接將生成好的SQL語句返回。所以MyBatis中,靜態(tài)SQL語句的執(zhí)行通常要快于動態(tài)SQL語句的執(zhí)行,這在RawSqlSource類的注釋中也有提及,如下所示。

Static SqlSource. It is faster than {@link DynamicSqlSource} because mappings are calculated during startup.

總結

MyBatis會為映射文件中的每個CURD標簽節(jié)點里的SQL語句生成一個SqlSource

  • 如果是靜態(tài)SQL語句,那么會生成RawSqlSource;
  • 如果是動態(tài)SQL語句,則會生成DynamicSqlSource

MyBatis在生成SqlSource時,會為CURD標簽節(jié)點的每個子節(jié)點都生成一個SqlNode,無論子節(jié)點是文本值節(jié)點還是動態(tài)SQL元素節(jié)點,最終所有子節(jié)點對應的SqlNode都會放在SqlSource中以供生成SQL語句使用。

如果是靜態(tài)SQL語句,那么在創(chuàng)建RawSqlSource時就會使用SqlNode完成SQL語句的生成以及將SQL語句中的#{}占位符替換為?,然后保存在RawSqlSource中,等到這條靜態(tài)SQL語句要被執(zhí)行時,就直接返回這條靜態(tài)SQL語句。

如果是動態(tài)SQL語句,在創(chuàng)建DynamicSqlSource時只會簡單的將SqlNode保存下來,等到這條動態(tài)SQL語句要被執(zhí)行時,才會使用SqlNode完成SQL語句的生成以及將SQL語句中的#{}占位符替換為?,最后返回SQL語句。

所以MyBatis中,靜態(tài)SQL語句的獲取要快于動態(tài)SQL語句。

以上就是詳解MyBatis的動態(tài)SQL實現原理的詳細內容,更多關于MyBatis 動態(tài)SQL實現的資料請關注腳本之家其它相關文章!

相關文章

  • springmvc下實現登錄驗證碼功能示例

    springmvc下實現登錄驗證碼功能示例

    本篇文章主要介紹了springmvc下實現登錄驗證碼功能示例,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • httpclient staleConnectionCheckEnabled獲取連接流程解析

    httpclient staleConnectionCheckEnabled獲取連接流程解析

    這篇文章主要為大家介紹了httpclient staleConnectionCheckEnabled獲取連接流程示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11
  • Java current并發(fā)包超詳細分析

    Java current并發(fā)包超詳細分析

    current并發(fā)包、在JDK1.5之前Java并沒有提供線程安全的一些工具類去操作多線程,需要開發(fā)人員自行編寫實現線程安全,但仍然無法完全避免低性能、死鎖、資源管理等問題。在JDK1.5時新增了java.util.current并發(fā)包,其中提供了許多供我們使用的并發(fā)編程工具類
    2023-02-02
  • SpringMVC 方法四種類型返回值總結(你用過幾種)

    SpringMVC 方法四種類型返回值總結(你用過幾種)

    這篇文章主要介紹了SpringMVC 方法四種類型返回值總結(你用過幾種),小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • SpringBoot在生產快速禁用Swagger2的方法步驟

    SpringBoot在生產快速禁用Swagger2的方法步驟

    這篇文章主要介紹了SpringBoot在生產快速禁用Swagger2的方法步驟,使用注解關閉Swagger2,避免接口重復暴露,非常具有實用價值,需要的朋友可以參考下
    2018-12-12
  • java實現word轉pdf or直接生成pdf文件

    java實現word轉pdf or直接生成pdf文件

    這篇文章主要介紹了java實現word轉pdf or直接生成pdf文件方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • SpringBoot-RestTemplate實現調用第三方API的方式

    SpringBoot-RestTemplate實現調用第三方API的方式

    RestTemplate?是由?Spring?提供的一個?HTTP?請求工具,它提供了常見的REST請求方案的模版,例如?GET?請求、POST?請求、PUT?請求、DELETE?請求以及一些通用的請求執(zhí)行方法?exchange?以及?execute,下面看下SpringBoot?RestTemplate調用第三方API的方式
    2022-12-12
  • SpringBoot添加SSL證書,開啟HTTPS方式(單向認證服務端)

    SpringBoot添加SSL證書,開啟HTTPS方式(單向認證服務端)

    這篇文章主要介紹了SpringBoot添加SSL證書,開啟HTTPS方式(單向認證服務端),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • java實現微信公眾號發(fā)送模版消息

    java實現微信公眾號發(fā)送模版消息

    這篇文章以訂單推送為例,主要為大家詳細介紹了java實現微信公眾號發(fā)送模版消息,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • @Data注解在Boolean類型屬性上的大坑及解決

    @Data注解在Boolean類型屬性上的大坑及解決

    在使用@Data注解時,如果類中存在Boolean類型的屬性,且屬性名不是以"is"開頭,那么@Data注解生成的get方法名會默認加上"is",導致屬性值無法成功拷貝,解決方法是手動添加get方法,覆蓋@Data注解生成的方法
    2024-10-10

最新評論

榕江县| 台北县| 吕梁市| 和硕县| 怀远县| 巴东县| 梁山县| 冷水江市| 都匀市| 固始县| 章丘市| 平遥县| 松江区| 搜索| 保康县| 嘉祥县| 兰考县| 大足县| 雷州市| 牡丹江市| 客服| 遂溪县| 柳江县| 大洼县| 拉孜县| 霍山县| 改则县| 吉安市| 上蔡县| 华安县| 晋中市| 株洲县| 西乌| 巴彦县| 黔江区| 抚远县| 永川市| 瑞丽市| 阳曲县| 山西省| 泸溪县|