在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

由 Mybatis 源碼暢談軟件設計(八):從根上理解 Mybatis 二級緩存

京東云 ? 來源:jf_75140285 ? 作者:jf_75140285 ? 2025-06-23 11:35 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

1. 驗證二級緩存

在上一篇帖子中的 User 和 Department 實體類依然要用,這里就不再贅述了,要啟用二級緩存,需要在 Mapper.xml 文件中指定 cache 標簽,如下:

UserMapper.xml

    
        select * from user
    

    
Department.xml

    
        select * from department;
    
    
    

在 Department.xml 中的 cache 標簽指定了 readOnly 屬性,因為該配置相對比較重要,所以我們在這里把它講解一下:

readOnly 默認為 false,這種情況下通過二級緩存查詢出來的數據會進行一次 序列化深拷貝。在這里大家需要回想一下介紹一級緩存時舉的例子:一級緩存查詢出來返回的是 該對象的引用,若我們對它修改,再查詢 時觸發一級緩存獲得的便是 被修改過的數據。但是,二級緩存的序列化機制則不同,它獲取到的是 緩存深拷貝的對象,這樣對二級緩存進行修改操作不影響后續查詢結果。

如果將該屬性配置為 true 的話,那么它就會變得和一級緩存一樣,返回的是對象的引用,這樣做的好處是 避免了深拷貝的開銷

為什么會有這種機制呢?

因為二級緩存是 Mapper級別 的,不能保證其他 SqlSession 不對二級緩存進行修改,所以這也是一種保護機制。

我們驗證一下這個例子,Department 和 User 的查詢都執行了兩遍(注意 事務提交之后 才能使二級緩存生效):

public static void main(String[] args) {
        InputStream xml = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 開啟二級緩存需要在同一個SqlSessionFactory下,二級緩存存在于 SqlSessionFactory 生命周期,如此才能命中二級緩存
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(xml);

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
        DepartmentMapper departmentMapper1 = sqlSession1.getMapper(DepartmentMapper.class);

        System.out.println("----------department第一次查詢 ↓------------");
        List departments1 = departmentMapper1.findAll();
        System.out.println("----------user第一次查詢 ↓------------");
        List users1 = userMapper1.findAll();

        // 提交事務,使二級緩存生效
        sqlSession1.commit();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
        DepartmentMapper departmentMapper2 = sqlSession2.getMapper(DepartmentMapper.class);

        System.out.println("----------department第二次查詢 ↓------------");
        List departments2 = departmentMapper2.findAll();
        System.out.println("----------user第二次查詢 ↓------------");
        List users2 = userMapper2.findAll();

        sqlSession1.close();
        sqlSession2.close();
}

Department 和 User 的同一條查詢語句都執行了兩遍,因為 Department 指定了 readOnly 為true,那么 兩次查詢返回的對象均為同一個引用,而 User 則反之,Debug 試一下:

wKgZO2hYy2iAY8CGAAKQAagnXFA590.png

cache 的其他屬性

屬性 描述 備注
eviction 緩存回收策略 默認 LRU
type 二級緩存的實現類 默認實現 PerpetualCache
size 緩存引用數量 默認1024
flushInterval 定時清除時間間隔 默認無
blocking 阻塞獲取緩存數據 若緩存中找不到對應的 key ,是否會一直阻塞,直到有對應的數據進入緩存。默認 false

接下來我們測試驗證下二級緩存的生效:

   SqlSession sqlSession1 = sqlSessionFactory.openSession();
   DepartmentMapper departmentMapper1 = sqlSession1.getMapper(DepartmentMapper.class);

   System.out.println("----------department第一次查詢 ↓------------");
   List departments1 = departmentMapper1.findAll();

   // 使二級緩存生效
   sqlSession1.commit();

   SqlSession sqlSession2 = sqlSessionFactory.openSession();
   DepartmentMapper departmentMapper2 = sqlSession2.getMapper(DepartmentMapper.class);

   System.out.println("----------department第二次查詢 ↓------------");
   List departments2 = departmentMapper2.findAll();

第一次 Query,會去數據庫中查

wKgZPGhYy2mAOPkcAAJL2vCN4Go519.png

第二次 Query,直接從二級緩存中取

wKgZO2hYy2qAfHtlAAZ_6zql_9w968.png

2. 二級緩存的原理

二級緩存對象 Cache

在加載 Mapper 文件(org.apache.ibatis.builder.xml.XMLConfigBuilder#mappersElement 方法)時,定義了加載 cache 標簽的步驟(org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement 方法),代碼如下:

public class XMLMapperBuilder extends BaseBuilder {
    // ...
    
    private void configurationElement(XNode context) {
        try {
            // 若想要在多個命名空間中共享相同的緩存配置和實例,可以使用 cache-ref 元素來引用另一個緩存
            cacheRefElement(context.evalNode("cache-ref"));
            // 配置二級緩存
            cacheElement(context.evalNode("cache"));
            // ...
        } catch (Exception e) {
            throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
    }
}

具體解析邏輯如下:

public class XMLMapperBuilder extends BaseBuilder {
    // ...
    
    private void cacheElement(XNode context) {
        if (context != null) {
            // 二級緩存實現類,默認 PerpetualCache,我們在一級緩存也提到過
            String type = context.getStringAttribute("type", "PERPETUAL");
            Class typeClass = typeAliasRegistry.resolveAlias(type);
            // 緩存清除策略,默認 LRU
            String eviction = context.getStringAttribute("eviction", "LRU");
            Class evictionClass = typeAliasRegistry.resolveAlias(eviction);
            // 定時清除間隔
            Long flushInterval = context.getLongAttribute("flushInterval");
            // 緩存引用數量
            Integer size = context.getIntAttribute("size");
            // readOnly上文我們提到過,默認 false
            boolean readWrite = !context.getBooleanAttribute("readOnly", false);
            // blocking 默認 false
            boolean blocking = context.getBooleanAttribute("blocking", false);
            Properties props = context.getChildrenAsProperties();
            // 創建緩存對象
            builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
    }
}

我們繼續看創建二級緩存對象的邏輯 org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache,可以發現,創建 Cache 對象使用了 建造者模式

wKgZPGhYy2uAIl6yAAFXBuJ7-SI640.png

建造者 CacheBuilder 并沒有被組合在任意一種緩存的實現類中,而是根據如下代碼中 implementation(valueOrDefault(typeClass, PerpetualCache.class)) 邏輯指定了要創建的緩存類型,并在 build 方法中使用反射創建對應實現類:

public class MapperBuilderAssistant extends BaseBuilder {
    // ...

    public Cache useNewCache(Class typeClass, Class evictionClass, Long flushInterval,
                             Integer size, boolean readWrite, boolean blocking, Properties props) {
        // 建造者模式,將標簽屬性賦值
        Cache cache = new CacheBuilder(currentNamespace).implementation(valueOrDefault(typeClass, PerpetualCache.class))
                .addDecorator(valueOrDefault(evictionClass, LruCache.class)).clearInterval(flushInterval).size(size)
                .readWrite(readWrite).blocking(blocking).properties(props).build();

        // 添加到全局配置中
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
    }
}

其中 addDecorator(valueOrDefault(evictionClass, LruCache.class)) 邏輯添加了 裝飾器,使用了 裝飾器模式,將 LruCache 類型的裝飾器添加到 decorators 中:

public class CacheBuilder {

    private final List> decorators;

    public CacheBuilder addDecorator(Class decorator) {
        // 將 LruCache 裝飾器添加到 decorators
        if (decorator != null) {
            this.decorators.add(decorator);
        }
        return this;
    }
    
    // ...
}

在 CacheBuilder#build 方法中,如下為封裝裝飾器的邏輯:

public class CacheBuilder {
    // ...

    public Cache build() {
        setDefaultImplementations();
        // 反射創建 PerpetualCache
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);

        // 封裝裝飾器的邏輯
        if (PerpetualCache.class.equals(cache.getClass())) {
            for (Class decorator : decorators) {
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            // 初始化基礎必要的裝飾器
            cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            cache = new LoggingCache(cache);
        }
        return cache;
    }
    

    private Cache setStandardDecorators(Cache cache) {
        try {
            MetaObject metaCache = SystemMetaObject.forObject(cache);
            if (size != null && metaCache.hasSetter("size")) {
                metaCache.setValue("size", size);
            }
            // 定時清空二級緩存
            if (clearInterval != null) {
                cache = new ScheduledCache(cache);
                ((ScheduledCache) cache).setClearInterval(clearInterval);
            }
            // readOnly屬性相關的讀寫緩存
            if (readWrite) {
                cache = new SerializedCache(cache);
            }
            // 日志緩存和同步緩存(借助 ReentrantLock 實現)
            cache = new LoggingCache(cache);
            cache = new SynchronizedCache(cache);
            // 阻塞屬性的緩存
            if (blocking) {
                cache = new BlockingCache(cache);
            }
            return cache;
        } catch (Exception e) {
            throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
        }
    }

}

所有裝飾器都在 org.apache.ibatis.cache.decorators 包下,唯獨 PerpetualCache 在org.apache.ibatis.cache.impl 包下:

wKgZO2hYy2yAQVjGAAOEnOdqUQ8112.png

PerpetualCache 中不包含 delegate 屬性表示裝飾器,說明它將作為最基礎的實現類被其他裝飾器裝飾,而其他裝飾器中均含有 delegate 屬性來裝飾其他實現。

默認創建的二級緩存類型如下:

wKgZPGhYy22ADgzKAAGFxHV9RUk052.png

類關系圖如下:

wKgZO2hYy2-AeKElAA3DkuC3ty4349.png

query 方法對二級緩存的應用

org.apache.ibatis.executor.CachingExecutor#query 方法使用了二級緩存,如下代碼所示:

public class CachingExecutor implements Executor {

  // 事務緩存管理器
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  
  @Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 先獲取二級緩存,該對象便是上文中創建的被裝飾器裝飾的 PerpetualCache
    Cache cache = ms.getCache();
    if (cache != null) {
      // 判斷是否需要清除緩存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 從二級緩存中取
        @SuppressWarnings("unchecked")
        List list = (List) tcm.getObject(cache, key);
        if (list == null) {
          // 沒取到二級緩存,嘗試取一級緩存或去數據庫查詢
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // “添加二級緩存”
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    
    // 沒有二級緩存的話,執行的是我們在一級緩存中介紹的方法,要么取一級緩存,否則去數據庫查
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
  
  // ...
}

上述邏輯比較清晰,我們在上文中提到過,只有 事務提交的時候才會將二級緩存保存,但是其中有 tcm.putObject(cache, key, list); 邏輯,似乎在這里保存了二級緩存,而此時事務還未提交,這便需要我們一探究竟。它會執行到 TransactionalCacheManager#putObject 方法:

public class TransactionalCacheManager {

    private final Map transactionalCaches = new HashMap();
    
    public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
    }
    
    private TransactionalCache getTransactionalCache(Cache cache) {
        return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
    }
}

TransactionalCacheManager 事務緩存管理器會創建并管理 TransactionalCache 對象,TransactionalCache 同樣是 Cache 裝飾器,它將裝飾在 SynchronizedCache 上:

public class TransactionalCache implements Cache {

    // 被裝飾對象,默認是 SynchronizedCache
    private final Cache delegate;
    // 該元素將保存在事務 commit 時被保存的鍵值對緩存
    private final Map entriesToAddOnCommit;

    @Override
    public void putObject(Object key, Object object) {
        entriesToAddOnCommit.put(key, object);
    }
    
    // ...
}

putObject 執行時便是向 entriesToAddOnCommit 添加元素,記錄二級緩存鍵值對,并沒有真正添加到二級緩存 PerpetualCache 對象中。此外,entriesToAddOnCommit 的命名,也暗示了在事務提交時緩存才會被保存。那么接下來,便需要看一下事務提交邏輯。

在上文測試二級緩存的代碼中,有 sqlSession1.commit(); 邏輯。在事務提交時,它會走到 CachingExecutor#commit 方法,其中會調用到 TransactionalCacheManager#commit 方法,如下:

public class CachingExecutor implements Executor {
    // ...
    
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    
    @Override
    public void commit(boolean required) throws SQLException {
        // ...
        tcm.commit();
    }
    
}

在該方法中,會遍歷所有的事務緩存 TransactionalCache,并逐一調用它們的 commit 方法,

public class TransactionalCacheManager {

    private final Map transactionalCaches = new HashMap();
    
    public void commit() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.commit();
        }
    }
    
    // ...

commit 方法會調用 delegate.commit 方法,而 delegate 為被裝飾對象,最后便會將二級緩存記錄:

public class TransactionalCache implements Cache {

    private final Map entriesToAddOnCommit;

    public void commit() {
        if (clearOnCommit) {
            delegate.clear();
        }
        flushPendingEntries();
        reset();
    }

    private void flushPendingEntries() {
        // 事務提交,將 entriesToAddOnCommit 中所有待添加的二級緩存添加
        for (Map.Entry entry : entriesToAddOnCommit.entrySet()) {
            delegate.putObject(entry.getKey(), entry.getValue());
        }
        for (Object entry : entriesMissedInCache) {
            if (!entriesToAddOnCommit.containsKey(entry)) {
                delegate.putObject(entry, null);
            }
        }
    }

    private void reset() {
        clearOnCommit = false;
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();
    }
    
    // ...
}

緩存失效

事務回滾是不是會使本次事務中相關的二級緩存失效呢?

public class TransactionalCache implements Cache {

    public void rollback() {
        unlockMissedEntries();
        reset();
    }

    private void reset() {
        clearOnCommit = false;
        entriesToAddOnCommit.clear();
        entriesMissedInCache.clear();
    }

    private void unlockMissedEntries() {
        for (Object entry : entriesMissedInCache) {
            try {
                delegate.removeObject(entry);
            } catch (Exception e) {
                log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
                        + "Consider upgrading your cache adapter to the latest version. Cause: " + e);
            }
        }
    }
    // ...
}

的確如此,它會將未被緩存的元素清除 reset(),也會把在本次事務中操作過的數據在二級緩存中移除 unlockMissedEntries()。

那數據發生新增、修改或刪除呢?同樣會清除緩存

public class CachingExecutor implements Executor {

    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
    }
    

    private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        // 默認 flushCacheRequired 為 true
        if (cache != null && ms.isFlushCacheRequired()) {
            tcm.clear(cache);
        }
    }

它將調用 TransactionalCache#clear 方法,將待生效的 entriesToAddOnCommit 二級緩存清除,并標記 clearOnCommit 為 true,在事務提交時,二級緩存會執行清除緩存的 clear 方法:


    @Override
    public void clear() {
        clearOnCommit = true;
        entriesToAddOnCommit.clear();
    }

    public void commit() {
        if (clearOnCommit) {
            delegate.clear();
        }
        flushPendingEntries();
        reset();
    }

緩存生效范圍

到這里,我們已經基本弄清楚二級緩存生效的原理了,那么接下來我們需要解釋“為什么二級緩存是 Mapper 級別的?”其實也非常簡單,看如下代碼:

public class CachingExecutor implements Executor {
  
  @Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 先獲取二級緩存,該對象便是上文中創建的被裝飾器裝飾的 PerpetualCache
    Cache cache = ms.getCache();
    // ...
  }
    // ...
}

在執行查詢時,二級緩存 Cache 是在 MappedStatement 中獲取的,Mapper 中每個 SQL 聲明都對應唯一的 MappedStatement,當同一條 SQL 被執行時,它們都會去取同樣的緩存,所以可以說它是 Mapper 級別的,說成 MappedStatement 級別更準確,二級緩存支持多個 SqlSession 共享。

為什么要在事務提交后才生效?

在這里我們討論一個問題:為什么二級要在事務提交后才能生效呢

因為二級緩存可以在不同的 SqlSession 間生效,畫個圖你就明白了:

wKgZO2hYy3CASnCGAAMDWZScyoU417.png

如果 SqlSession1先修改了數據再查詢數據,如果二級緩存在事務未提交時就生效,那么 SqlSession2 調用同樣的查詢時便會從 二級緩存中獲取數據,但是此時 SqlSession1回滾了事務,那么此時就會導致 SqlSession2 從二級緩存獲取的數據 變成臟數據,這就是為什么二級緩存要在事務提交后才能生效的原因。

3. 為什么要擴展二級緩存?

MyBatis 中設計一級緩存和二級緩存的目的是為了提高數據庫訪問的效率,但它們的作用范圍和使用場景有所不同,各自有其特定的用途和優勢。

一級緩存 默認開啟,是基于 SqlSession 的,也就是說,它的作用范圍僅限于一次數據庫會話,所以當會話關閉后,緩存就會被清除。這意味著不同會話之間無法共享緩存數據。而 二級緩存 是基于 Mapper 級別的,需要顯式配置開啟,可以在多個 SqlSession 之間共享。當然也由于二級緩存的作用范圍更廣,因此需要更復雜的緩存失效策略和數據一致性管理,以避免數據不一致的問題。二級緩存的引入是為了在更大范圍內(多個會話之間)提高數據訪問的效率,特別是在讀多寫少的應用場景。

4. 總結

二級緩存本質上是 HashMap,在 PerpetualCache 實現類中

二級緩存是 Mapper 級別的,可以在不同 SqlSession 間共享

特殊的 readOnly 標簽,默認為 false,表示二級緩存中是被深拷貝的對象

二級緩存需要在事務提交后才能生效

執行 Insert、Delete、Update 語句會使 當前 Mapper 下的二級緩存失效

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 源碼
    +關注

    關注

    8

    文章

    669

    瀏覽量

    30239
  • mybatis
    +關注

    關注

    0

    文章

    64

    瀏覽量

    6891
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    蔡司培訓|提升技能必看——AUKOM 一/二級課程培訓

    AUKOM 一課程通過系統的分析測量誤差,即工件、環境、測量機、操作人員、測量策略,五個方面分析誤差因子,保證策量結果的準確性。 AUKOM 二級課程是在AUKOM 一的基礎,更
    發表于 06-03 14:26 ?766次閱讀
    蔡司培訓|提升技能必看——AUKOM 一<b class='flag-5'>級</b>/<b class='flag-5'>二級</b>課程培訓

    二級浪涌電路之退耦電感選型

    二級浪涌防雷電路設計
    的頭像 發表于 05-12 15:31 ?270次閱讀
    <b class='flag-5'>二級</b>浪涌電路之退耦電感選型

    如何一眼定位SQL的代碼來源:一款SQL染色標記的簡易MyBatis插件

    作者:京東物流 郭忠強 導語 本文分析了后端研發和運維在日常工作中所面臨的線上SQL定位排查痛點,基于姓名貼的靈感,設計和開發了一款SQL染色標記的MyBatis插件。該插件輕量高效,對業務代碼無
    的頭像 發表于 03-05 11:36 ?380次閱讀
    如何一眼定位SQL的代碼來源:一款SQL染色標記的簡易<b class='flag-5'>MyBatis</b>插件

    Mybatis 源碼暢談軟件設計(九):“能用就行” 其實遠遠不夠

    作者:京東保險 王奕龍 到本節 Mybatis 源碼中核心邏輯基本已經介紹完了,在這里我想借助 Mybatis 其他部分源碼來介紹一些我認為在編程中能 最快提高編碼質量的小方法 ,它們
    的頭像 發表于 01-03 10:39 ?399次閱讀

    MediaTek發布旗艦天璣8400處理器

    近日,聯發科正式發布了其新一代旗艦全大核處理器——天璣8400。這款芯片集成了個基于ARM架構的A725 CPU核心,性能強勁。 據悉,天璣8400在緩存方面進行了全面升級,二級緩存
    的頭像 發表于 12-24 09:39 ?759次閱讀

    SSM框架的源碼解析與理解

    SSM框架(Spring + Spring MVC + MyBatis)是一種在Java開發中常用的輕量級企業應用框架。它通過整合Spring、Spring MVC和MyBatis三個框架,實現了
    的頭像 發表于 12-17 09:20 ?930次閱讀

    CDCE62005第二級PLL無法鎖住的原因?如何解決?

    我們遇到兩片CDCE62005聯后,部分板卡第二級芯片Lock信號失鎖問題。 我們的芯片電路是完全參考TI官方6678開發板的,配置的SPI接口FPGA控制,配置文件官方
    發表于 11-11 07:06

    二級浪涌保護器的區別與選型指南

    尤為重要。地凱科技將深入分析一二級浪涌保護器的主要區別、識別方法、選擇依據及不同行業中的細分應用。 一浪涌保護器與二級浪涌保護器的定義與工作原理 一
    的頭像 發表于 11-08 11:04 ?1002次閱讀
    一<b class='flag-5'>級</b>和<b class='flag-5'>二級</b>浪涌保護器的區別與選型指南

    二級配電箱的作用介紹

    配電箱作為電能分配和管理的關鍵設備,發揮著至關重要的作用。二級配電箱位于一配電箱和三配電箱之間,起到中間分配電力的作用。本文將深入探討二級配電箱的主要特點和功能,為電力系統的優化提
    的頭像 發表于 10-04 11:46 ?1361次閱讀

    物聯網系統中如何增強GNSS的信號_GNSS二級放大電路研發測試方案

    降低設備工作功耗的效果。 02 該問題帶來的危害及影響 如果不在原有電路板飛線測試射頻二級放大電路射頻性能,定位功能,不認真制定測試方案,直接設計PCB,打板貼片,有可能導致電路無法到達預期要求,造成時間與金錢的浪費,導致項目開發時間的拖
    的頭像 發表于 09-30 18:25 ?1119次閱讀
    物聯網系統中如何增強GNSS的信號_GNSS<b class='flag-5'>二級</b>放大電路研發測試方案

    INA128兩放大,第二級放大測不出來的原因?如何解決?

    放大 第一放大6倍第二級放大16倍 信號源輸入2v電壓示波器顯示第一放大只有5.2倍第二級放大測不出來當兩
    發表于 09-12 06:51

    THS3001聯組成放大電路,實際接通后第二級有明顯發熱,為什么?

    實際接通后第二級有明顯發熱。單獨測試第一沒問題,對第二級直接輸入第一的輸出相關參數依然正常。可是兩塊一旦級聯第二級就發熱。
    發表于 09-06 06:08

    浪涌保護器和二級浪涌保護器怎么區分

    浪涌保護器和二級浪涌保護器是電力系統中非常重要的保護設備,它們的主要作用是保護電力系統免受雷電、操作過電壓等浪涌電壓的損害。在電力系統中,浪涌保護器的設置和選擇對于系統的安全穩定運行
    的頭像 發表于 07-13 14:44 ?6525次閱讀

    二級浪涌保護器型號如何選擇

    選擇二級浪涌保護器型號時,需要綜合考慮多個因素,以確保所選型號能夠滿足電器系統的實際需求。以下是一些關鍵步驟和考慮因素: 一、了解系統需求 電壓等級 :首先,明確您的電器系統的電壓等級,這是選擇浪涌
    的頭像 發表于 07-13 14:25 ?1402次閱讀

    使用mybatis切片實現數據權限控制

    一、使用方式 數據權限控制需要對查詢出的數據進行篩選,對業務入侵最少的方式就是利用mybatis或者數據庫連接池的切片對已有業務的sql進行修改。切片邏輯完成后,僅需要在業務中加入少量標記代碼
    的頭像 發表于 07-09 17:26 ?691次閱讀
    使用<b class='flag-5'>mybatis</b>切片實現數據權限控制
    主站蜘蛛池模板: 欧美色图中文字幕 | 激情综合五月婷婷 | 免费色站 | 欧美人与z0xxxx另类 | 哪里可以看免费毛片 | 91av在线免费观看 | 韩国三级中文 | 天天躁狠狠躁夜夜躁2021 | 欧美草比 | 黄色免费网站在线播放 | 婷婷深爱五月 | 中韩日欧美电影免费看 | 视频二区在线观看 | 天天色天天色 | 人人澡人人射 | 天堂资源在线 | 久久国产美女免费观看精品 | 中文字幕不卡免费高清视频 | 黄色a网| 欧美极品在线观看 | 午夜视频免费观看 | 午夜精品久久久久久久久 | 激情综合视频 | 亚a在线| 黄网站色视频 | 啪啪日韩| 欧美三级中文字幕hd | 最好看的最新中文字幕2018免费视频 | 日韩一卡2卡三卡4卡无卡网站 | 精品日韩一区二区三区 | 中国女人a毛片免费全部播放 | 丁香花在线影院观看在线播放 | 一区二区在线观看高清 | 日本人的xxxxxxxxx69 | 极品吹潮视频大喷潮tv | 美女色18片黄黄色 | 国产大片免费观看资源 | 扒开双腿猛进湿润18p | 日韩精品视频免费观看 | 91福利国产在线观看网站 | 天天视频免费入口 |