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

Mybatis 的 Interceptor(攔截器) 與 JSqlparser 結(jié)合解析SQL 使SpringBoot項(xiàng)目多數(shù)據(jù)庫兼容的嘗試(推薦)

 更新時(shí)間:2026年01月08日 09:22:25   作者:劉子丙  
MyBatis插件提供了在SQL執(zhí)行前進(jìn)行攔截、擴(kuò)展的功能,允許用戶自定義邏輯處理,本文介紹Mybatis 的 Interceptor(攔截器) 與 JSqlparser 結(jié)合解析SQL 使SpringBoot項(xiàng)目多數(shù)據(jù)庫兼容的嘗試(推薦),感興趣的朋友跟隨小編一起看看吧

Mybaits插件簡單描述

插件簡介和具體作用

Mybatis官方提供了插件機(jī)制為用戶開辟了一道可以自定義的攔截?cái)U(kuò)展功能,在 系統(tǒng)最終執(zhí)行SQL 之前,分別有四個(gè)部位可以做擴(kuò)展,允許用戶在不修改Mybatis核心代碼的情況下,添加自己的邏輯處理,去完成各種各樣的業(yè)務(wù)場景

官方簡介:mybatis – MyBatis 3 | 配置 

典型案例:

1.MyBatis 分頁插件 PageHelper 

2.分頁插件 | MyBatis-Plus (baomidou.com)

業(yè)務(wù)場景示例

  1. 性能監(jiān)控:記錄SQL執(zhí)行時(shí)間,統(tǒng)計(jì)慢查詢,幫助開發(fā)者發(fā)現(xiàn)并優(yōu)化性能瓶頸
  2. 訪問控制:根據(jù)用戶的權(quán)限,動(dòng)態(tài)修改SQL語句,實(shí)現(xiàn)數(shù)據(jù)行級或列級的權(quán)限控制。
  3. 動(dòng)態(tài)SQL注入:根據(jù)不同的業(yè)務(wù)需求,動(dòng)態(tài)拼接或修改SQL語句。
  4. 數(shù)據(jù)脫敏:在查詢結(jié)果返回給客戶端之前,對敏感信息進(jìn)行脫敏處理。
  5. 緩存控制:對查詢結(jié)果進(jìn)行緩存,減少數(shù)據(jù)庫訪問次數(shù),提高系統(tǒng)性能。
  6. 自定義分頁:用戶也可以像pageHelper、mybatis-plus的分頁那樣自定義自己的分頁
  7. 加密解密:利用這個(gè)機(jī)制,對數(shù)據(jù)庫中敏感字段的對稱加密,諸如密碼、卡號(hào)、身份信息之類的
  8. 自定義日志輸出:攔截SQL執(zhí)行,實(shí)現(xiàn)自定義的日志輸出格式,便于問題追蹤和調(diào)試。
  9. 動(dòng)態(tài)數(shù)據(jù)源切換:在攔截器中根據(jù)業(yè)務(wù)規(guī)則,動(dòng)態(tài)選擇不同的數(shù)據(jù)源。
  10. 參數(shù)校驗(yàn):在執(zhí)行SQL語句前,對參數(shù)進(jìn)行校驗(yàn),確保數(shù)據(jù)的完整性和一致性。

四個(gè)核心組件簡介

這四個(gè)部位其實(shí)就是Mybatis的四大核心組件:

  1. Executor (執(zhí)行器):負(fù)責(zé)增刪改查和事務(wù),它調(diào)度(另外三個(gè))StatementHandlerParameterHandler、ResultSetHandler等來執(zhí)行對應(yīng)的SQL
  2. StatementHandler(語句預(yù)處理) : 封裝JDBC,構(gòu)建SQL語法,負(fù)責(zé)和數(shù)據(jù)庫進(jìn)行交互執(zhí)行sql語句,(后期下手操作和修改SQL也主要是以它為主)
  3. ParameterHandler (參數(shù)處理): 負(fù)責(zé)將參數(shù)真正加入到SQL語句中的部分
  4. ResultSetHandler (結(jié)果處理) :負(fù)責(zé)將JDBC查詢結(jié)果映射到j(luò)ava對象

以下是流程圖:

攔截方法劃分

精細(xì)劃分的目的是為了允許在數(shù)據(jù)庫操作的不同階段進(jìn)行精確的干預(yù)和攔截

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
                Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
                Object.class,
                RowBounds.class,
                ResultHandler.class})})
@Component
public class MyApplicationInterceptor implements Interceptor {
       // 具體實(shí)現(xiàn)內(nèi)容...
}

可攔截的方法:

  • update:負(fù)責(zé)執(zhí)行 insert、update、delete 三種類型的 SQL 語句。
  • query:負(fù)責(zé)執(zhí)行 select 類型的 SQL 語句。
  • queryCursor:負(fù)責(zé)執(zhí)行 select 類型的 SQL 語句,返回 Cursor 對象。
  • flushStatements:提交批處理語句,返回批處理結(jié)果。
  • commit:事務(wù)提交。
  • rollback:事務(wù)回滾。
  • getTransaction:獲取事務(wù)對象。
  • close:關(guān)閉 executor,同時(shí)根據(jù)參數(shù)決定是否強(qiáng)制回滾未提交的事務(wù)。
  • isClosed:檢查 executor 是否已經(jīng)關(guān)閉。
  • clearLocalCache:清除本地緩存。
ParameterHandler (getParameterObject, setParameters)
@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@Slf4j
public class ParameterPluginInterceptor implements Interceptor {}

  • 可攔截的方法:
    • getParameterObject:此方法用于獲取 SQL 參數(shù)對象。
    • setParameters:此方法將 SQL 命令中的參數(shù)與實(shí)際的參數(shù)對象相匹配。它負(fù)責(zé)將傳入的參數(shù)設(shè)置到 PreparedStatement 中。
StatementHandler (prepare, parameterize, batch, update, query)
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Slf4j
public class StatementPluginInterceptor implements Interceptor {}
  • prepare:準(zhǔn)備一個(gè)數(shù)據(jù)庫 Statement 對象以待執(zhí)行。這個(gè)方法根據(jù)配置和上下文信息來創(chuàng)建一個(gè) PreparedStatement 或 CallableStatement 對象。
  • parameterize:在 SQL 語句被執(zhí)行之前,該方法負(fù)責(zé)將 SQL 參數(shù)設(shè)置到 PreparedStatement 對象中。
  • batch:負(fù)責(zé)處理批量執(zhí)行的邏輯,將多個(gè)更新語句作為一個(gè)批處理提交。
  • update:執(zhí)行寫操作(insert、update、delete)的 SQL 語句。
  • query:執(zhí)行查詢操作(select)的 SQL 語句,并返回結(jié)果。
  • queryCursor:負(fù)責(zé)執(zhí)行查詢操作(select)SQL 語句,返回 Cursor 對象。
  • getBoundSql:返回 BoundSql 對象,這個(gè)對象包含了要執(zhí)行的 SQL 語句以及該語句中所需的參數(shù)信息。
ResultSetHandler (handleResultSets, handleOutputParameters)
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
})
/*@Component*/
@Slf4j
public class ResultInterceptor implements Interceptor {
  • handleResultSets:這是主要的方法之一,它接受一個(gè) Statement 對象作為參數(shù),并將 SQL執(zhí)行的結(jié)果 ResultSet 映射到結(jié)果對象。
  • handleOutputParameters:當(dāng)存儲(chǔ)過程調(diào)用完成之后,這個(gè)方法會(huì)處理其輸出參數(shù)。它同樣接受一個(gè) Statement 對象作為參數(shù)。

攔截器實(shí)現(xiàn):

如何實(shí)現(xiàn)一個(gè)自己的攔截器

implements Interceptor  以實(shí)現(xiàn)一個(gè)Mybatis的攔截器,之后必須實(shí)現(xiàn)以上三個(gè)方法

  1. intercept 攔截 : 主要實(shí)現(xiàn)攔截邏輯的地方,也就是用戶拿來自定義業(yè)務(wù)的地方
  2. plugin:包裝方法,用來創(chuàng)建代理對象
  3. setProperties:配置方法,用來設(shè)置攔截器的屬性

以下是一個(gè)空白的Executor攔截:集成Interceptor之后,再加入注解內(nèi)容選擇要攔截的部分

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
                Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
                Object.class,
                RowBounds.class,
                ResultHandler.class})})
@Component
@Slf4j
public class ExecutorInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement)  invocation.getTarget();
        log.info("==> ExecutorInterceptor: {}", mappedStatement.getId());
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

Invocation中有什么

以下是一個(gè)使用mybaits interceptor攔截器,攔截StatementHandler

可以看到 invocation 中已經(jīng)夾了很多做攔截器需要的內(nèi)容了

最重要的東西就是

  • BoundSql(解析后的sql對象) 首先它能拿到這次執(zhí)行中的SQL,其次就是參數(shù),等同于拿到一個(gè)完全可以執(zhí)行的SQL語句。很多IDEA里Mybaits log這樣的插件就是根據(jù)這個(gè)東西這么來的。
  • BoundSql—ParameterObject對象 顧名思義,就是SQL的參數(shù)對象,它直接接受到我們從外界傳到內(nèi)部的參數(shù)對象,它是一個(gè)Object,根據(jù)傳遞的類型而改變
  • BoundSql—ParameterMapping對象 再mybatis的 mapper xml中寫的每一個(gè)#{var},最后都會(huì)形成一個(gè) ?,這和我們直接使用jdbc是一個(gè)道理,最終都是挨個(gè)往 ?(問號(hào)) 里面塞值,只不過ParameterMapping記錄了這些變量,它會(huì)在SQL最終要執(zhí)行的時(shí)候去設(shè)置參數(shù)

實(shí)現(xiàn)一個(gè)簡單的SQL語句攔截

以下實(shí)現(xiàn)了一個(gè)簡單的攔截器,攔截MappedStatement

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.util.Properties;
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,
                Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class,
                Object.class,
                RowBounds.class,
                ResultHandler.class})})
@Component
@Slf4j
public class ExecutorInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf('.'));
        String methodName = id.substring(id.lastIndexOf('.') + 1);
        BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
        Object parameterObject = boundSql.getParameterObject();
        log.info("==> id: {}",id);
        log.info("==> ClassName: {}", className);
        log.info("==> MethodName: {}", methodName);
        log.info("==> SQL語句: {}", boundSql.getSql());
        log.info("==> 參數(shù): {}", parameterObject);
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
        Interceptor.super.setProperties(properties);
    }
}

最終效果:(可以看到,打印了SQL的來源,包、方法、SQL語句、參數(shù)都拿到了)

攔截SQL執(zhí)行的最終結(jié)果

ResultSetHandler是對SQL最終操作的結(jié)果映射到j(luò)ava中的步驟,以下代碼的操作,最終將攔截這個(gè)步驟,并對MAP相關(guān)結(jié)果做出操作,將MAP中的Key都轉(zhuǎn)成大寫

@Slf4j
@Component
@Intercepts(
        {@Signature(
                type = ResultSetHandler.class,
                method = "handleResultSets",
                args = {Statement.class}
        )})
public class ResultMapCaseInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
        MetaObject metaResultSetHandler = MetaObject.forObject(resultSetHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
        MappedStatement mappedStatement = (MappedStatement) metaResultSetHandler.getValue("mappedStatement");
        Object returnValue = invocation.proceed();
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        String resultMapTypeName = resultMaps.get(0).getType().getName();
        String MAP = "java.util.Map";
        String HASH_MAP = "java.util.HashMap";
        String LINKED_HASH_MAP = "java.util.LinkedHashMap";
        if(StrUtil.equalsIgnoreCase(MAP, resultMapTypeName) ||
                StrUtil.equalsIgnoreCase(HASH_MAP, resultMapTypeName) ||
                StrUtil.equalsIgnoreCase(LINKED_HASH_MAP, resultMapTypeName)){
            if(returnValue instanceof List<?> list){
                if(CollectionUtil.isNotEmpty(list)) {
                    list.forEach(item -> {
                            Map<String, Object> map = (Map<String, Object>) item;
                            Map<String, Object> newMap = new LinkedHashMap<>();
                            map.forEach((k, v) -> {
                                    newMap.put(k.toUpperCase(), v);
                            });
                            map.clear();
                            map.putAll(newMap);
                    });
                }
            }
        }
        return returnValue;
    }
    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }
    @Override
    public void setProperties(Properties properties) {
    }
}

使用JSQLparser解析SQL語句

簡述及安裝

JSqlParser 是一個(gè) SQL 語句解析器。它轉(zhuǎn)換 Java 類的可遍歷層次結(jié)構(gòu)中的 SQL。JSqlParser不僅限于一個(gè)數(shù)據(jù)庫,而是支持Oracle,SqlServer,MySQL,PostgreSQL等等,至今它仍在更新,而Mybatisplus之中也包含了這個(gè)庫,可以直接使用,如果沒有使用mybatis plus那么需要手動(dòng)引入它

官方網(wǎng)站:JSQLParser 4.9 documentation 

Github:JSQLParser/JSqlParser(github.com)

Maven:

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.9</version>
</dependency>

Gradle/KT:

implementation("com.github.jsqlparser:jsqlparser:4.9")
簡單的使用

以下是一個(gè)簡單的例子,為了展示Jsqlparser可以解析SQL的內(nèi)容

    @Test
    public void Test2() throws JSQLParserException, ParseException {
        String originalSql = "SELECT " +
                "t1.id, " +
                "t1.name, " +
                "SUM(t2.amount) AS total_amount, " +
                "(SELECT COUNT(*) FROM orders o WHERE o.customer_id = t1.id) AS order_count " +
                "FROM customers t1 " +
                "JOIN orders t2 ON t1.id = t2.customer_id " +
                "LEFT JOIN payments t3 ON t2.id = t3.order_id " +
                "WHERE t1.status = 'active' " +
                "AND t2.order_date BETWEEN '2023-01-01' AND '2023-12-31' " +
                "GROUP BY t1.id, t1.name " +
                "HAVING SUM(t2.amount) > 1000 " +
                "ORDER BY total_amount DESC, t1.name ASC;";
        CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
        Statement statement = parser.Statement();
        parser.getASTRoot().jjtAccept(sqlModifier, null);
        log.info("==> JsqlParser SQL: {}", statement.toString());
        Select selectStatement = (Select) statement;
        PlainSelect plainSelect = selectStatement.getPlainSelect();
        Table table = (Table) plainSelect.getFromItem();
        System.out.println(table.toString());
        //獲取表名們
        Set<String> tableNames = TablesNamesFinder.findTables(originalSql);
        System.out.println("表名們:"+tableNames);
        // Print SELECT clause
        System.out.println("SELECT clause: " + plainSelect.getSelectItems());
        // Print FROM clause
        System.out.println("FROM clause: " + plainSelect.getFromItem());
        // Print JOIN clauses
        if (plainSelect.getJoins() != null) {
            for (Join join : plainSelect.getJoins()) {
                System.out.println("JOIN clause: " + join);
            }
        }
        // Print WHERE clause
        System.out.println("WHERE clause: " + plainSelect.getWhere());
        // Print GROUP BY clause
        System.out.println("GROUP BY clause: " + plainSelect.getGroupBy());
        // Print HAVING clause
        System.out.println("HAVING clause: " + plainSelect.getHaving());
        // Print ORDER BY clause
        System.out.println("ORDER BY clause: " + plainSelect.getOrderByElements());
    }
直接使用

以下是一個(gè)簡單的使用,在代碼里使用Jsqlparser,最直接的方式就是直接調(diào)用CCJSqlParserUtil

String originalSql = "select * from t_user" // 需要解析的SQL語句
CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
Statement statement = parser.Statement();
拆解SQL語句

通過 getPlainSelect() 獲得Statement中的PlainSelect,而這個(gè)PlainSelect就可以拿到很多SQL語句中的內(nèi)容

 獲取各種子元素
        String originalSql = "SELECT rrr.* FROM rel_role_resource rrr " +
                "JOIN rel_role_user rru ON rrr.role_id = rru.role_id " +
                "JOIN `user` u ON u.id = rru.user_id " +
                "AND rrr.org_id = ? " +
                "AND rrr.role_id = rru.role_id " +
                "AND u.id = 123";
        CCJSqlParser parser = CCJSqlParserUtil.newParser(originalSql);
        Statement statement = parser.Statement();
        Select select = (Select) statement;
        log.info("==> Select: {}", select.toString());
        PlainSelect plain = select.getPlainSelect();
        plain.getSelectItems().forEach(selectItem -> {
            log.info("==> 查詢的字段: {}", selectItem.toString());
        });
        if (plain.getFromItem() != null) {
            log.info("==> 查詢的表: {}", plain.getFromItem().toString());
        }
        if (plain.getWhere() != null){
            log.info("==> 查詢的條件: {}", plain.getWhere().toString());
        }
        if(CollectionUtil.isNotEmpty(plain.getJoins())){
            plain.getJoins().forEach(join -> {
                join.getOnExpressions().forEach(expression -> {
                    log.info("==> Join 條件: {}", expression.toString());
                });
                log.info("==> Join: {}", join.toString());
            });
        }
        TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
        Set<String> tableList = tablesNamesFinder.getTables(statement);
        tableList.forEach(table -> {
            log.info("==> 查詢的表: {}", table);
        });
        log.info("==> 查詢的表: {}", tableList);

運(yùn)行后結(jié)果:

關(guān)于Jsqlparser的具體使用,可以看另外一篇博客【JSqlParser】Java使用JSqlParser解析SQL語句總結(jié)

總結(jié)

使用Springboot + mybatis + Jsqlparser,通過Mybatis的interceptor,可以獲取到整個(gè)項(xiàng)目中通過mybatis執(zhí)行的SQL語句,最終在StatementHandler 中截取SQL,對截取到的SQL進(jìn)行分析,再通過Jsqlparser的visit 對SQL 的不同位置,例如替換 引號(hào)、特殊字符、函數(shù)等,做處理,最終可以達(dá)到數(shù)據(jù)庫遷移并適配的效果

到此這篇關(guān)于Mybatis 的 Interceptor(攔截器) 與 JSqlparser 結(jié)合解析SQL 使SpringBoot項(xiàng)目多數(shù)據(jù)庫兼容的嘗試(推薦)的文章就介紹到這了,更多相關(guān)Mybatis Interceptor攔截器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • springboot使用nacos的示例詳解

    springboot使用nacos的示例詳解

    這篇文章主要介紹了springboot使用nacos的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-12-12
  • 使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理的方法

    使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理的方法

    Stream 作為 Java 8 的一大亮點(diǎn),它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念這篇文章主要介紹了使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理,需要的朋友可以參考下
    2022-07-07
  • 解決Feign調(diào)用的GET參數(shù)傳遞的問題

    解決Feign調(diào)用的GET參數(shù)傳遞的問題

    這篇文章主要介紹了解決Feign調(diào)用的GET參數(shù)傳遞的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Spring Boot集成MyBatis的方法

    Spring Boot集成MyBatis的方法

    今天小編就為大家分享一篇關(guān)于Spring Boot集成MyBatis的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • RabbitMQ實(shí)現(xiàn)延遲通知的兩種方案

    RabbitMQ實(shí)現(xiàn)延遲通知的兩種方案

    延遲通知是指消息在發(fā)送后不會(huì)立即被消費(fèi),而是在指定的時(shí)間延遲后才被處理的消息傳遞機(jī)制,
    2025-10-10
  • java 中http請求為了防止亂碼解決方案

    java 中http請求為了防止亂碼解決方案

    這篇文章主要介紹了java 中http請求為了防止亂碼解決方案的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • 解決Nacos在執(zhí)行startup.cmd的時(shí)候出現(xiàn)閃退的問題

    解決Nacos在執(zhí)行startup.cmd的時(shí)候出現(xiàn)閃退的問題

    因?yàn)樵诠ぷ髦械捻?xiàng)目中需要使用到nacos作為注冊中心,但是在使用nacos的過程中運(yùn)行startup.cmd的時(shí)候出現(xiàn)了閃退的情況,運(yùn)行startup.cmd閃一下就沒有了,我把解決這個(gè)問題的全過程理了一下,希望能幫到您,需要的朋友可以參考下
    2023-12-12
  • Spring事務(wù)失效的8大原因詳解

    Spring事務(wù)失效的8大原因詳解

    這篇文章主要介紹了Spring事務(wù)失效的8大原因詳解,這里以 MySQL 為例,其 MyISAM 引擎是不支持事務(wù)操作的,InnoDB 才是支持事務(wù)的引擎,一般要支持事務(wù)都會(huì)使用 InnoDB,需要的朋友可以參考下
    2023-09-09
  • 基于IDEA中格式化代碼的快捷鍵分享

    基于IDEA中格式化代碼的快捷鍵分享

    這篇文章主要介紹了基于IDEA中格式化代碼的快捷鍵分享,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Jackson中json格式的字符串與對象的互相轉(zhuǎn)換方式

    Jackson中json格式的字符串與對象的互相轉(zhuǎn)換方式

    這篇文章主要介紹了Jackson中json格式的字符串與對象的互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07

最新評論

隆尧县| 营山县| 克什克腾旗| 彭山县| 普陀区| 黄平县| 宜宾县| 南康市| 华安县| 沛县| 长丰县| 宜章县| 鱼台县| 确山县| 平南县| 慈利县| 敦化市| 新竹县| 芒康县| 岱山县| 墨脱县| 勃利县| 射阳县| 古蔺县| 栾城县| 潮安县| 青铜峡市| 清丰县| 亚东县| 开鲁县| 阳东县| 凤山市| 石渠县| 镇宁| 揭西县| 新闻| 石首市| 平陆县| 青冈县| 婺源县| 安阳市|