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

淺談MyBatis 如何執(zhí)行一條 SQL語(yǔ)句

 更新時(shí)間:2021年05月14日 14:23:05   作者:天地創(chuàng)造技術(shù)部  
Mybatis 是 Java 開(kāi)發(fā)中比較常用的 ORM 框架。在日常工作中,我們都是直接通過(guò) Spring Boot 自動(dòng)配置,并直接使用,但是卻不知道 Mybatis 是如何執(zhí)行一條 SQL 語(yǔ)句的,下面就一起講解一下

前言

Mybatis 是 Java 開(kāi)發(fā)中比較常用的 ORM 框架。在日常工作中,我們都是直接通過(guò) Spring Boot 自動(dòng)配置,并直接使用,但是卻不知道 Mybatis 是如何執(zhí)行一條 SQL 語(yǔ)句的,而這篇文章就是來(lái)揭開(kāi) Mybatis 的神秘面紗。

基礎(chǔ)組件

我們要理解 Mybatis 的執(zhí)行過(guò)程,就必須先了解 Mybatis 中都有哪一些重要的類(lèi),這些類(lèi)的職責(zé)都是什么?

SqlSession

我們都很熟悉,它對(duì)外提供用戶(hù)和數(shù)據(jù)庫(kù)之間交互需要使用的方法,隱藏了底層的細(xì)節(jié)。它默認(rèn)是實(shí)現(xiàn)類(lèi)是 DefaultSqlSession

Executor

這個(gè)是執(zhí)行器,SqlSession 中對(duì)數(shù)據(jù)庫(kù)的操作都是委托給它。它有多個(gè)實(shí)現(xiàn)類(lèi),可以使用不同的功能。

Configuration

它是一個(gè)很重要的配置類(lèi),它包含了 Mybatis 的所有有用信息,包括 xml 配置,動(dòng)態(tài) sql 語(yǔ)句等等,我們到處都可以看到這個(gè)類(lèi)的身影。

MapperProxy

這是一個(gè)很重要的代理類(lèi),它代理的就是 Mybatis 中映射 SQL 的接口。也就是我們常寫(xiě)的 Dao 接口。

工作流程

初步使用

首先,我們需要得到一個(gè) SqlSessionFactory 對(duì)象,該對(duì)象的作用是可以獲取 SqlSession  對(duì)象。

// 讀取配置
InputStream resourceAsStream = Resources.getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 創(chuàng)建一個(gè) SqlSessionFactory 對(duì)象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

當(dāng)我們得到一個(gè) SqlSessionFactory 對(duì)象之后,就可以通過(guò)它的 openSession 方法得到一個(gè) SqlSession 對(duì)象。

 SqlSession sqlSession = sqlSessionFactory.openSession(true);

最后,我們通過(guò) SqlSession 對(duì)象獲取 Mapper ,從而可以從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)。

// 獲取 Mapper 對(duì)象
HeroMapper mapper = sqlSession.getMapper(HeroMapper.class);
// 執(zhí)行方法,從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)
Hero hero = mapper.selectById(1);

詳細(xì)流程

獲取 MapperProxy 對(duì)象

我們現(xiàn)在主要關(guān)注的就是 getMapper 方法,該方法為我們創(chuàng)建一個(gè)代理對(duì)象,該代理對(duì)象為我們執(zhí)行 SQL 語(yǔ)句提供了重要的支持。

// SqlSession 對(duì)象
@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

getMapper  方法里面委托 Configuration 對(duì)象去獲取對(duì)應(yīng)的 Mapper 代理對(duì)象,之前說(shuō)過(guò) Configuration 對(duì)象里面包含了 Mybatis 中所有重要的信息,其中就包括我們需要的 Mapper 代理對(duì)象,而這些信息都是在讀取配置信息的時(shí)候完成的,也就是執(zhí)行sqlSessionFactoryBuilder.build 方法。

// Configuration 對(duì)象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

我們可以看到它又將獲取 Mapper 代理對(duì)象的操作委托給了 MapperRegistry 對(duì)象(擱著俄羅斯套娃呢?),這個(gè) MapperRegistry 對(duì)象里面就存放了我們想要的 Mapper 代理對(duì)象,如果你這么想,就錯(cuò)了,實(shí)際上,它存放的并不是我們想要的 Mapper 代理對(duì)象,而是 Mapper 代理對(duì)象的工廠(chǎng),Mybatis 這里使用到了工廠(chǎng)模式。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}

我只保留了 getMapper 方法和 addMapper 方法。

在 getMapper 方法中,它獲取的是 MapperProxyFactory 對(duì)象,我們通過(guò)名稱(chēng)可以得出這是一個(gè) Mapper 代理對(duì)象工廠(chǎng),但是我們是要得到一個(gè) MapperProxy 對(duì)象,而不是一個(gè)工廠(chǎng)對(duì)象,我們?cè)賮?lái)看 getMapper 方法,它通過(guò) mapperProxyFactory.newInstance 來(lái)創(chuàng)建代理對(duì)象。

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

創(chuàng)建了一個(gè) MapperProxy 對(duì)象,并且通過(guò) Proxy.newProxyInstance 方法(不會(huì)還有人不知道這是 JDK 動(dòng)態(tài)代理吧),創(chuàng)建一個(gè)代理對(duì)象處理,這個(gè)代理對(duì)象就是我們想要的結(jié)果。這里沒(méi)有體現(xiàn)出來(lái)代理了哪個(gè)對(duì)象?。科鋵?shí) mapperInterface 這是一個(gè)成員變量,它引用了需要被代理的對(duì)象。而這個(gè)成員變量實(shí)在創(chuàng)建 MapperProxyFactory 對(duì)象的時(shí)候賦值的,所以我們每一個(gè)需要被代理的接口,在 Mybatis 中都會(huì)為它生成一個(gè) MapperProxyFactory 對(duì)象,該對(duì)象的作用就是為了創(chuàng)建所需要的代理對(duì)象。

緩存執(zhí)行方法

當(dāng)我們獲取到代理對(duì)象 mapper 之后,就可以執(zhí)行它里面的方法。
這里使用一個(gè)例子:

// Myabtis 所需要的接口
public interface HeroMapper {
    Hero selectById(Integer id);
}
// HeroMapper 接口所對(duì)應(yīng)的 xml 文件
<?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="test.HeroMapper">
    <select id="selectById" resultType="test.Hero">
        select * from hero where id = #{id}
    </select>
</mapper>

我們執(zhí)行 selectById 方法,獲取一個(gè)用戶(hù)的信息。

// 獲取 Mapper 對(duì)象
HeroMapper mapper = sqlSession.getMapper(HeroMapper.class);
// 執(zhí)行方法,從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù)
Hero hero = mapper.selectById(1);

通過(guò)上面的解析已經(jīng)知道,這里的 mapper 是一個(gè)代理對(duì)象的引用,而這個(gè)代理類(lèi)則是 MapperProxy,所以我們主要是去了解 MapperProxy 這個(gè)代理類(lèi)做了什么事情。

public class MapperProxy<T> implements InvocationHandler, Serializable {
    
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
      return methodCache.computeIfAbsent(method, m -> {
           return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
  }
    
  private static class PlainMethodInvoker implements MapperMethodInvoker {
      private final MapperMethod mapperMethod;

      public PlainMethodInvoker(MapperMethod mapperMethod) {
          super();
          this.mapperMethod = mapperMethod;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return mapperMethod.execute(sqlSession, args);
      }
  }
}

代理對(duì)象執(zhí)行方法時(shí)都是直接執(zhí)行 invoke() 方法,在這個(gè)方法中,我們主要就看一條語(yǔ)句 cachedInvoker(method).invoke(proxy, method, args, sqlSession);

我們首先看 cachedInvoker 方法,它的參數(shù)是 Method 類(lèi)型,所以這個(gè) method 表示的就是我們執(zhí)行的方法 HeroMapper.selectById,它首先從緩存中獲取是否之前已經(jīng)創(chuàng)建過(guò)一個(gè)該方法的方法執(zhí)行器 PlainMethodInvoker 對(duì)象,其實(shí)這只是一個(gè)包裝類(lèi),可有可無(wú),在工程上來(lái)說(shuō),有了這個(gè)包裝類(lèi),會(huì)更加易于維護(hù)。而這個(gè)執(zhí)行器里面只有一個(gè)成員對(duì)象,這個(gè)成員對(duì)象就是 MapperMethod,并且這個(gè) MapperMethod 的構(gòu)造函數(shù)中需要傳遞  HeroMapper、HeroMapper.selectById、Cofiguration 這三個(gè)參數(shù)。

以上步驟都執(zhí)行完成之后,接下來(lái)我們可以看到執(zhí)行了 PlainMethodInvoker 的 invoke 方法,而它又將真正的操作委托給了 MapperMethod,執(zhí)行 MapperMethod 下的 execute 方法,這個(gè)方法就是本文章的重點(diǎn)所在。

構(gòu)造參數(shù)

從上面的解析可以知道,最后會(huì)執(zhí)行到這個(gè)方法之中。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    return result;
  }

這個(gè)方法中,我們可以看到熟悉的幾個(gè)關(guān)鍵字:select、update、delete、insert,這個(gè)就是為了找到執(zhí)行方式,我們因?yàn)槭?select 語(yǔ)句,所以分支會(huì)走向 select,并且最終會(huì)執(zhí)行到 sqlSession.selectOne 方法中,所以最終饒了一大圈,依然還是回到了我們一開(kāi)始就提到的 SqlSession 對(duì)象中。
在這個(gè)方法中,首先會(huì)構(gòu)造參數(shù),也就是我們看到的 convertArgsToSqlCommandParam 方法,它的內(nèi)部執(zhí)行方式是按照如下方式來(lái)轉(zhuǎn)換參數(shù)的:

使用 @param 自定義命名
amethod(@Param int a, @Param int b)  則會(huì)構(gòu)造 map  ->  [{"a", a_arg}, {"b", b_arg}, {"param1",  a_arg}, {"param2", b_arg}],a 和 param1 是對(duì)參數(shù) a 的命名,a_arg 是傳遞的實(shí)際的值。
雖然只有兩個(gè)參數(shù),但是最后卻會(huì)在 Map 存在四個(gè)鍵值對(duì),因?yàn)?Mybatis 最后自己會(huì)生成以 param 為前綴的參數(shù)名稱(chēng),名稱(chēng)按照參數(shù)的位置進(jìn)行命名。

不使用 @param

amethod(int a, int b),則會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}],因?yàn)闆](méi)有對(duì)參數(shù)進(jìn)行自定義命名,所以 Myabtis 就對(duì)參數(shù)取了一個(gè)默認(rèn)的名稱(chēng),以 arg 為前綴,位置為后綴進(jìn)行命名。

在參數(shù)只有一個(gè),并且參數(shù)為集合的情況下,會(huì)存放多個(gè)鍵值對(duì):

  • amethod(Collection<Integer> a),這種情況下,會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"collection", a_arg}]
  • amethod(List<Integer> a),這種情況下,會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"collection", a_arg}, {"list", a_arg}]
  • amethod(Integer[] a),這種情況下,會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"array", a_arg}]
  • 但是,如果有兩個(gè)參數(shù),那么就不會(huì)這么存放,而是按照常規(guī)的方式:
  • amethod(List<Integer> a,List<Integer> b)  則會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}]
  • amethod(List<Integer> a,int b)  則會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}]

不會(huì)作為參數(shù)的對(duì)象
在 Mybatis 中有兩個(gè)特殊的對(duì)象:RowBounds、ResultHandler,這兩個(gè)對(duì)象如果作為參數(shù)則不會(huì)放入到 map 中,但是會(huì)占據(jù)位置。

amethod(int a,RowBounds rb, int b),這種情況下,會(huì)構(gòu)造 map -> [{"arg0", a_arg}, {"arg2", b_arg}, {"param1", a_arg}, {"param2", b_arg}]

注意這里的 b 參數(shù)的命名分別是 arg2 和 param2,arg2 是因?yàn)樗奈恢迷趨?shù)的第 3 位,而 param2 則是因?yàn)樗堑?2 個(gè)有效參數(shù)。

獲取需要執(zhí)行的 SQL 對(duì)象

參數(shù)構(gòu)造完成之后,我們就需要尋找需要執(zhí)行的 SQL 語(yǔ)句了。

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

這里的 statement 雖然是 String 類(lèi)型的,但是它并不是真正的 SQL 語(yǔ)句,它是一個(gè)尋找對(duì)應(yīng) MapperStatement 對(duì)象的名稱(chēng),在我們的例子中,它就是 test.HeroMapper.selectById ,Mybatis 通過(guò)這個(gè)名稱(chēng)可以尋找到包含了 SQL 語(yǔ)句的對(duì)象。

我們跟蹤代碼的執(zhí)行,最后會(huì)來(lái)到下面這個(gè)方法,這是一個(gè)包含三個(gè)參數(shù)的重載方法。

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

在第四行代碼中,可以得知它通過(guò) statement 從 Configuration 對(duì)象中獲取了一個(gè) MapperStatement 對(duì)象, MapperStatement 對(duì)象包含的信息是由 <select>、<update>、<delete> 、<insert> 元素提供的,我們?cè)谶@些元素中定義的信息都會(huì)保存在該對(duì)象中,如:Sql 語(yǔ)句、resultMap、fetchSize 等等。

執(zhí)行 SQL 語(yǔ)句

獲取到包含 SQL 語(yǔ)句信息的對(duì)象之后,就會(huì)交給 Execute 執(zhí)行器對(duì)象去執(zhí)行后續(xù)的處理,也就是 executor.query 方法。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

獲取需要自行的 Sql 語(yǔ)句,然后創(chuàng)建一個(gè)緩存使用的 key,用于二級(jí)緩存。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // ....
    // 跟緩存有關(guān),如果緩存中存在數(shù)據(jù),則直接從緩存中返回,否則從數(shù)據(jù)庫(kù)中查詢(xún)
 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
 return list;
}

最后會(huì)執(zhí)行到一個(gè) doQuery 方法

@Override
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);
        stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
    } finally {
        closeStatement(stmt);
    }
}

這段代碼創(chuàng)建了一個(gè) Statement 對(duì)象的處理器 StatementHandler,這個(gè)處理器主要的工作就是完成 JDBC 中 PrepareStatement 對(duì)象的一些準(zhǔn)備工作,包括:創(chuàng)建 PrepareStatement 對(duì)象,設(shè)置需要執(zhí)行的 sql 語(yǔ)句,為 sql 語(yǔ)句中的參數(shù)賦值。完成這些工作之后,就開(kāi)始從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)了。

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
}

第四行代碼即執(zhí)行對(duì)應(yīng)的 Sql 查詢(xún),后續(xù)則是對(duì)結(jié)果進(jìn)行處理。

總結(jié)

Mybatis 通過(guò) MapperProxy 代理了我們的 Dao 接口類(lèi),以此來(lái)幫助我們執(zhí)行預(yù)定義的 Sql 語(yǔ)句,通過(guò) Cache 來(lái)緩存對(duì)應(yīng)的執(zhí)行結(jié)果,通過(guò) StatementHandler  創(chuàng)建 PrepareStatement 對(duì)象,通過(guò) jdbc 執(zhí)行 SQL 操作。

到此這篇關(guān)于淺談MyBatis 如何執(zhí)行一條 SQL語(yǔ)句的文章就介紹到這了,更多相關(guān)MyBatis 執(zhí)行SQL語(yǔ)句內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解決IDEA報(bào)錯(cuò)war?exploded?is?not?valid問(wèn)題

    解決IDEA報(bào)錯(cuò)war?exploded?is?not?valid問(wèn)題

    在使用IntelliJ?IDEA時(shí)遇到'[projectname]warexploded'無(wú)效的問(wèn)題,可以通過(guò)清除項(xiàng)目列表、重新導(dǎo)入項(xiàng)目和配置新的Tomcat來(lái)解決,確保在Tomcat配置中,將ApplicationContext修改為僅包含一個(gè)'/',這一方法或許能幫助遇到相似問(wèn)題的開(kāi)發(fā)者
    2024-09-09
  • Java多線(xiàn)程之定時(shí)器Timer的實(shí)現(xiàn)

    Java多線(xiàn)程之定時(shí)器Timer的實(shí)現(xiàn)

    這篇文章主要為大家詳細(xì)介紹了Java多線(xiàn)程中定時(shí)器Timer類(lèi)的使用以及模擬實(shí)現(xiàn),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-10-10
  • Java使用Semaphore對(duì)單接口進(jìn)行限流

    Java使用Semaphore對(duì)單接口進(jìn)行限流

    本篇主要講如何使用Semaphore對(duì)單接口進(jìn)行限流,主要有三種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-07-07
  • 實(shí)戰(zhàn)分布式醫(yī)療掛號(hào)系統(tǒng)開(kāi)發(fā)醫(yī)院科室及排班的接口

    實(shí)戰(zhàn)分布式醫(yī)療掛號(hào)系統(tǒng)開(kāi)發(fā)醫(yī)院科室及排班的接口

    這篇文章主要為大家介紹了實(shí)戰(zhàn)分布式醫(yī)療掛號(hào)系統(tǒng)開(kāi)發(fā)醫(yī)院科室及排班的接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2022-04-04
  • SpringBoot3實(shí)現(xiàn)webclient的通用方法詳解

    SpringBoot3實(shí)現(xiàn)webclient的通用方法詳解

    Spring Boot WebClient 是 Spring Framework 5 中引入的一個(gè)新的響應(yīng)式 Web 客戶(hù)端,用于異步和響應(yīng)式地與外部服務(wù)進(jìn)行通信,下面我們就來(lái)看看SpringBoot3實(shí)現(xiàn)webclient的通用方法吧
    2024-04-04
  • Java String的intern方法使用場(chǎng)景示例

    Java String的intern方法使用場(chǎng)景示例

    這篇文章主要介紹了Java String的intern方法使用場(chǎng)景示例,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-11-11
  • 情人節(jié)寫(xiě)給女朋友Java Swing代碼程序

    情人節(jié)寫(xiě)給女朋友Java Swing代碼程序

    這篇文章主要為大家分享了情人節(jié)寫(xiě)給女朋友的java小程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,祝大家每天都是情人節(jié)
    2018-02-02
  • Java中的==和equals()區(qū)別小結(jié)

    Java中的==和equals()區(qū)別小結(jié)

    在Java編程中,理解==操作符和equals()方法的區(qū)別是至關(guān)重要的,本文主要介紹了Java中的==和equals()區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-08-08
  • 使用SpringCloudAlibaba整合Dubbo

    使用SpringCloudAlibaba整合Dubbo

    這篇文章主要介紹了使用SpringCloudAlibaba整合Dubbo,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • SpringAop @Aspect織入不生效,不執(zhí)行前置增強(qiáng)織入@Before方式

    SpringAop @Aspect織入不生效,不執(zhí)行前置增強(qiáng)織入@Before方式

    這篇文章主要介紹了SpringAop @Aspect織入不生效,不執(zhí)行前置增強(qiáng)織入@Before方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評(píng)論