0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

由 Mybatis 源码畅谈软件设计(八):从根上理解 Mybatis 二级缓存

京东云 来源:jf_75140285 作者:jf_75140285 2025-06-23 11:35 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

1. 验证二级缓存

在上一篇帖子中的 User 和 Department 实体类依然要用,这里就不再赘述了,要启用二级缓存,需要在 Mapper.xml 文件中指定 cache 标签,如下:

UserMapper.xml

    < select id="findAll" resultType="User" >
        select * from user
    < /select >

    < cache / >
Department.xml

    < select id="findAll" resultType="entity.Department" >
        select * from department;
    < /select >
    
    < cache readOnly="true"/ >

在 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< Department > departments1 = departmentMapper1.findAll();
        System.out.println("----------user第一次查询 ↓------------");
        List< User > 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< Department > departments2 = departmentMapper2.findAll();
        System.out.println("----------user第二次查询 ↓------------");
        List< User > 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< Department > departments1 = departmentMapper1.findAll();

   // 使二级缓存生效
   sqlSession1.commit();

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

   System.out.println("----------department第二次查询 ↓------------");
   List< Department > 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< ? extends Cache > typeClass = typeAliasRegistry.resolveAlias(type);
            // 缓存清除策略,默认 LRU
            String eviction = context.getStringAttribute("eviction", "LRU");
            Class< ? extends Cache > 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< ? extends Cache > typeClass, Class< ? extends Cache > 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< Class< ? extends Cache >> decorators;

    public CacheBuilder addDecorator(Class< ? extends Cache > 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< ? extends Cache > 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 < E > List< E > 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< E > list = (List< E >) 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< Cache, TransactionalCache > 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< Object, Object > 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< Cache, TransactionalCache > 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< Object, Object > entriesToAddOnCommit;

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

    private void flushPendingEntries() {
        // 事务提交,将 entriesToAddOnCommit 中所有待添加的二级缓存添加
        for (Map.Entry< Object, Object > 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 < E > List< E > 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

    文章

    675

    浏览量

    30461
  • mybatis
    +关注

    关注

    0

    文章

    64

    浏览量

    6974
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    缓存之美:万文详解 Caffeine 实现原理(

    文章将采用“总-分-总”的结构对配置固定大小元素驱逐策略的 Caffeine 缓存进行介绍,首先会讲解它的实现原理,在大家对它有一个概念之后再深入具体源码的细节之中,理解它的设计理念,从中能学习到
    的头像 发表于 08-05 14:49 176次阅读
    <b class='flag-5'>缓存</b>之美:万文详解 Caffeine 实现原理(<b class='flag-5'>上</b>)

    缓存之美:理解 ConcurrentHashMap

    本文将详细介绍 ConcurrentHashMap 构造方法、添加值方法和扩容操作等源码实现。 ConcurrentHashMap 是线程安全的哈希表,此哈希表的设计主要目的是在最小化更新操作对哈希
    的头像 发表于 08-05 14:48 130次阅读

    蔡司培训|提升技能必看——AUKOM 一/二级课程培训

    AUKOM 一课程通过系统的分析测量误差,即工件、环境、测量机、操作人员、测量策略,五个方面分析误差因子,保证策量结果的准确性。 AUKOM 二级课程是在AUKOM 一的基础,更
    发表于 06-03 14:26 903次阅读
    蔡司培训|提升技能必看——AUKOM 一<b class='flag-5'>级</b>/<b class='flag-5'>二级</b>课程培训

    二级浪涌电路之退耦电感选型

    二级浪涌防雷电路设计
    的头像 发表于 05-12 15:31 736次阅读
    <b class='flag-5'>二级</b>浪涌电路之退耦电感选型

    如何一眼定位SQL的代码来源:一款SQL染色标记的简易MyBatis插件

    作者:京东物流 郭忠强 导语 本文分析了后端研发和运维在日常工作中所面临的线上SQL定位排查痛点,基于姓名贴的灵感,设计和开发了一款SQL染色标记的MyBatis插件。该插件轻量高效,对业务代码无
    的头像 发表于 03-05 11:36 484次阅读
    如何一眼定位SQL的代码来源:一款SQL染色标记的简易<b class='flag-5'>MyBatis</b>插件

    网线怎么接水晶头

    网线接水晶头的方法通常遵循国际标准T568B或T568A线序,以下是基于T568B标准的详细步骤: 一、准备工具和材料 压线钳 水晶头 网线(已剥去外皮,露出内部线芯)
    的头像 发表于 03-03 11:03 2306次阅读

    Mybatis 源码畅谈软件设计(九):“能用就行” 其实远远不够

    作者:京东保险 王奕龙 到本节 Mybatis 源码中核心逻辑基本已经介绍完了,在这里我想借助 Mybatis 其他部分源码来介绍一些我认为在编程中能 最快提高编码质量的小方法 ,它们
    的头像 发表于 01-03 10:39 463次阅读

    MediaTek发布旗舰天玑8400处理器

    近日,联发科正式发布了其新一代旗舰全大核处理器——天玑8400。这款芯片集成了个基于ARM架构的A725 CPU核心,性能强劲。 据悉,天玑8400在缓存方面进行了全面升级,二级缓存
    的头像 发表于 12-24 09:39 904次阅读

    SSM框架的源码解析与理解

    SSM框架(Spring + Spring MVC + MyBatis)是一种在Java开发中常用的轻量级企业应用框架。它通过整合Spring、Spring MVC和MyBatis三个框架,实现了
    的头像 发表于 12-17 09:20 1050次阅读

    CDCE62005第二级PLL无法锁住的原因?如何解决?

    我们遇到两片CDCE62005联后,部分板卡第二级芯片Lock信号失锁问题。 我们的芯片电路是完全参考TI官方6678开发板的,配置的SPI接口FPGA控制,配置文件官方
    发表于 11-11 07:06

    二级浪涌保护器的区别与选型指南

    尤为重要。地凯科技将深入分析一二级浪涌保护器的主要区别、识别方法、选择依据及不同行业中的细分应用。 一浪涌保护器与二级浪涌保护器的定义与工作原理 一
    的头像 发表于 11-08 11:04 1276次阅读
    一<b class='flag-5'>级</b>和<b class='flag-5'>二级</b>浪涌保护器的区别与选型指南

    二级配电箱的作用介绍

    配电箱作为电能分配和管理的关键设备,发挥着至关重要的作用。二级配电箱位于一配电箱和三配电箱之间,起到中间分配电力的作用。本文将深入探讨二级配电箱的主要特点和功能,为电力系统的优化提
    的头像 发表于 10-04 11:46 1679次阅读

    物联网系统中如何增强GNSS的信号_GNSS二级放大电路研发测试方案

    降低设备工作功耗的效果。 02 该问题带来的危害及影响 如果不在原有电路板飞线测试射频二级放大电路射频性能,定位功能,不认真制定测试方案,直接设计PCB,打板贴片,有可能导致电路无法到达预期要求,造成时间与金钱的浪费,导致项目开发时间的拖
    的头像 发表于 09-30 18:25 1401次阅读
    物联网系统中如何增强GNSS的信号_GNSS<b class='flag-5'>二级</b>放大电路研发测试方案

    INA128两放大,第二级放大测不出来的原因?如何解决?

    放大 第一放大6倍第二级放大16倍 信号源输入2v电压示波器显示第一放大只有5.2倍第二级放大测不出来当两
    发表于 09-12 06:51

    THS3001联组成放大电路,实际接通后第二级有明显发热,为什么?

    实际接通后第二级有明显发热。单独测试第一没问题,对第二级直接输入第一的输出相关参数依然正常。可是两块一旦级联第二级就发热。
    发表于 09-06 06:08