mybati缓存了解

news2025/1/16 16:01:44

title: “mybati缓存了解”
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
draft: false
author: “ggball”
tags: [“mybatis”]
categories: [“java”]
description: “mybati缓存了解”

mybatis的缓存

首先来看下mybatis对缓存的规范,规范嘛就是定义的接口啦。

缓存接口

​ Cache接口 定义了缓存的方法

public interface Cache {

  /**获取缓存的id
   * @return The identifier of this cache
   */
  String getId();

  /**添加缓存
   * @param key Can be any object but usually it is a {@link CacheKey}
   * @param value The result of a select.
   */
  void putObject(Object key, Object value);

  /**根据缓存键获取缓存
   * @param key The key
   * @return The object stored in the cache.
   */
  Object getObject(Object key);

  /**移除缓存
   * As of 3.3.0 this method is only called during a rollback 
   * for any previous value that was missing in the cache.
   * This lets any blocking cache to release the lock that 
   * may have previously put on the key.
   * A blocking cache puts a lock when a value is null 
   * and releases it when the value is back again.
   * This way other threads will wait for the value to be 
   * available instead of hitting the database.
   *
   * 
   * @param key The key
   * @return Not used
   */
  Object removeObject(Object key);

  /**
   * Clears this cache instance
   */  
  void clear();

  /**
   * Optional. This method is not called by the core.
   * 
   * @return The number of elements stored in the cache (not its capacity).
   */
  int getSize();
  
  /** 
   * Optional. As of 3.2.6 this method is no longer called by the core.
   *  
   * Any locking needed by the cache must be provided internally by the cache provider.
   * 
   * @return A ReadWriteLock 
   */
  ReadWriteLock getReadWriteLock();

}

实现

image-20211116203147375

mybatis实现了多种缓存,比如perpetualCache 是Cache接口的默认实现,通过hashMap来操作缓存,logginCache,在具有缓存的功能下,添加了打印日志的功能。

  • BlockingCache:阻塞版本的缓存装饰器,能够保证同一时间只有一个线程到缓存中查找指定的Key对应的数据。

  • FifoCache:先入先出缓存装饰器,FifoCache内部有一个维护具有长度限制的Key键值链表(LinkedList实例)和一个被装饰的缓存对象,Key值链表主要是维护Key的FIFO顺序,而缓存存储和获取则交给被装饰的缓存对象来完成。

  • LoggingCache:为缓存增加日志输出功能,记录缓存的请求次数和命中次数,通过日志输出缓存命中率。LruCache:最近最少使用的缓存装饰器,当缓存容量满了之后,使用LRU算法淘汰最近最少使用的Key和Value。

  • LruCache中通过重写LinkedHashMap类的removeEldestEntry()方法获取最近最少使用的Key值,将Key值保存在LruCache类的eldestKey属性中,然后在缓存中添加对象时,淘汰eldestKey对应的Value值。具体实现细节读者可参考LruCache类的源码。

  • ScheduledCache:自动刷新缓存装饰器,当操作缓存对象时,如果当前时间与上次清空缓存的时间间隔大于指定的时间间隔,则清空缓存。清空缓存的动作由getObject()、putObject()、removeObject()等方法触发。

  • SerializedCache:序列化缓存装饰器,向缓存中添加对象时,对添加的对象进行序列化处理,从缓存中取出对象时,进行反序列化处理。

  • SoftCache:软引用缓存装饰器,SoftCache内部维护了一个缓存对象的强引用队列和软引用队列,缓存以软引用的方式添加到缓存中,并将软引用添加到队列中,获取缓存对象时,如果对象已经被回收,则移除Key,如果未被回收,则将对象添加到强引用队列中,避免被回收,如果强引用队列已经满了,则移除最早入队列的对象的引用。

  • SynchronizedCache:线程安全缓存装饰器,SynchronizedCache的实现比较简单,为了保证线程安全,对操作缓存的方法使用synchronized关键字修饰。

  • TransactionalCache:事务缓存装饰器,该缓存与其他缓存的不同之处在于,TransactionalCache增加了两个方法,即commit()和rollback()。当写入缓存时,只有调用commit()方法后,缓存对象才会真正添加到TransactionalCache对象中,如果调用了rollback()方法,写入操作将被回滚。WeakCache:弱引用缓存装饰器,功能和SoftCache类似,只是使用不同的引用类型。

mybatis一级缓存

概念:

会话(session)级别的缓存称为一级缓存,默认开启的。

为什么使用一级缓存?

mybatis毕竟是查询数据库的一个半orm框架,查询数据库势必要消耗服务器的性能,为了减少服务器的性能,使用了缓存。将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。

实现原理自己的概括

当程序与数据库建立了一次会话,中间开始查询数据,每次查询会根据mapper的id、命名空间、sql等等创建缓存key,先去查询本地缓存是否有值,如果有值,则获取解析值,返回,如果没有值,则去查询数据库,再把结果缓存到本地缓存。

一级缓存实现原理

image-20211116205926071

首先来看下缓存实例是存在哪里的,在BaseExecutor中有本地缓存localCache,所以继承BaseExecutor的执行器都有localCache,包括但不限于

SimpleExecutor、BatchExecutor,ReuseExecutor。

接下来大概介绍查询流程,具体介绍用到一级缓存的地方

  @Test
    public  void testMybatisCache () throws IOException {
        // 获取配置文件输入流
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 调用openSession()方法创建SqlSession实例
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 获取UserMapper代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 执行Mapper方法,获取执行结果
        List<UserEntity> userList = userMapper.listAllUser();

        UserMapper userMapper1 = sqlSession.getMapper(UserMapper.class);
        // 执行Mapper方法,获取执行结果
        List<UserEntity> userList1 = userMapper.listAllUser();

        System.out.println(JSON.toJSONString(userList));
    }

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取Mybatis主配置文件配置的环境信息
      final Environment environment = configuration.getEnvironment();
      // 创建事务管理器工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 创建事务管理器
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 根据Mybatis主配置文件中指定的Executor类型创建对应的Executor实例
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建DefaultSqlSession实例
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  1. 从mybatis配置文件获取配置信息输入流,然后利用SqlSessionFactoryBuilder的build的方法创建SqlSession工厂
  2. openSession方法创建默认SqlSession(获取mybatis环境信息,创建事务管理器,创建执行器,构造默认SqlSession)
  3. 调用sqlSession的getMapper方法,利用动态代理(实现InvocationHadler)创建代理对象
  4. 调用Mapper的方法,实际上就是调用代理对象的invoke方法,而且调用查询方法(不管是默认还是自己写的),最后都会调用sqlSession的select相关方法。

根据上面的流程可以知道,SqlSession,Executor,localcache之间的关系

image-20211116211707062

接下来主要看SqlSession的select方法

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  
    @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取BoundSql对象,BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建CacheKey,用于缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用重载的query()方法
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 从缓存中获取结果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 缓存中获取不到,则调用queryFromDatabase()方法从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // 所有参数值
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
      }
    }
    // Environment Id
    if (configuration.getEnvironment() != null) {
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
    }
    return cacheKey;
  }
  1. 调用SqlSession的select方法,从configuration拿出mappedSatement(里面封装了mapper的属性),调用内置的执行器的query方法
  2. 执行器的query方法,先获取BoundSql对象,创建cacheKey(利用MapperId,偏移量,SQL语句…),调用重载的query
  3. 先根据cacheKey去执行器的localCache查询是否有值,如果没有再调用queryFromDatabase查询数据库,缓存结果到localCache。注意:LocalCacheScope=SATATEMENT时,每次查询都会清空缓存。

注意:在分布式环境下,务必将MyBatis的localCacheScope属性设置为STATEMENT,避免其他应用节点执行SQL更新语句后,本节点缓存得不到刷新而导致的数据一致性问题。

在MyBatis中,关于缓存设置的参数一共有2个:localCacheScope,cacheEnabled。

<!-- 二级缓存开关 有效值: true|false,默认值为true -->
<settingname="cacheEnabled"value="true"/>
<!-- 是否清除一级缓存 SESSION不清除,STATEMENT清除  有效值:SESSION|STATEMENT,默认值为SESSION -->
<settingname="localCacheScope"value="SESSION"/>

mybatis二级缓存

概念

二级缓存是全局的缓存,即使不同会话之间也能共享二级缓存,默认是不开启的;

二级缓存实现原理

首先说下如何开启他,在mybatis配置文件添加<settingname="cacheEnabled"value="true"/> 和在对应的mapper.xml添加cache实例

image-20211116213954482

image-20211116213939859

其次看下二级缓存是如何生效的

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // 根据executor类型创建对象的Executor对象
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 执行拦截器链的拦截逻辑
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

  @Override
  public Transaction getTransaction() {
    return delegate.getTransaction();
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      //issues #499, #524 and #573
      if (forceRollback) { 
        tcm.rollback();
      } else {
        tcm.commit();
      }
    } finally {
      delegate.close(forceRollback);
    }
  }


  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 调用createCacheKey()方法创建缓存Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.queryCursor(ms, parameter, rowBounds);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取MappedStatement对象中维护的二级缓存对象
    Cache cache = ms.getCache();
    if (cache != null) {
      // 判断是否需要刷新二级缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        // 从MappedStatement对象对应的二级缓存中获取数据
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // 如果缓存数据不存在,则从数据库中查询数据
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 將数据存放到MappedStatement对象对应的二级缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

}

public class TransactionalCacheManager {
  // 通过HashMap对象维护二级缓存对应的TransactionalCache实例
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    // 获取二级缓存对应的TransactionalCache对象,然后根据缓存Key获取缓存对象
    return getTransactionalCache(cache).getObject(key);
  }
  
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    // 获取二级缓存对应的TransactionalCache对象
    TransactionalCache txCache = transactionalCaches.get(cache);
    if (txCache == null) {
      // 如果获取不到则创建,然后添加到Map中
      txCache = new TransactionalCache(cache);
      transactionalCaches.put(cache, txCache);
    }
    return txCache;
  }

}


  1. 还是在sqlSessionFactory.openSession(); 时,会创建执行器,当cacheEnabled属性为ture,会创建CachingExecutor缓存执行器。

  2. 看下CachingExecutor的结构,它包含了一个委托执行器(使用了委托模式),用来真正执行的sql,而自己主要的作用是放在了建立和使用二级缓存

  3. 当执行sql,最后会进入到执行器,如果执行器是CachingExecutor时,会调用他的query方法,进入方法后,首先会从MappedStatement拿出二级缓存实例(你以为这就是二级缓存?不,你错了),然后判断是否要刷新缓存,再根据二级缓存实例从缓存管理器(CacheExecutor维护了TransactionalCacheManager缓存管理器,缓存管理器里面维护了二级缓存实例和TransactionalCache的关系)中得到TransactionalCache,再利用cacheKey获取TransactionalCache中对应的二级缓存,如果缓存不存在,则使用委托执行器去数据库查询数据,再缓存结果,如果存在,则直接返回。

    再回过头看,当时的MappedStatement是如何get二级缓存实例的;

      private void configurationElement(XNode context) {
        try {
          // 获取命名空间
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          // 设置当前正在解析的Mapper配置的命名空间
          builderAssistant.setCurrentNamespace(namespace);
          // 解析<cache-ref>标签
          cacheRefElement(context.evalNode("cache-ref"));
          // 解析<cache>标签
          cacheElement(context.evalNode("cache"));
          // 解析所有的<parameterMap>标签
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          // 解析所有的<resultMap>标签
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          // 解析所有的<sql>标签
          sqlElement(context.evalNodes("/mapper/sql"));
    

createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
createTime: 2021-12-08T12:19:57+08:00
updateTime: 2021-12-08T12:19:57+08:00
} catch (Exception e) {
throw new BuilderException(“Error parsing Mapper XML. The XML location is '” + resource + "'. Cause: " + e, e);
}
}

 private void cacheElement(XNode context) throws Exception {
   if (context != null) {
     String type = context.getStringAttribute("type", "PERPETUAL");
     Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
     String eviction = context.getStringAttribute("eviction", "LRU");
     Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
     Long flushInterval = context.getLongAttribute("flushInterval");
     Integer size = context.getIntAttribute("size");
     boolean readWrite = !context.getBooleanAttribute("readOnly", false);
     boolean blocking = context.getBooleanAttribute("blocking", false);
     Properties props = context.getChildrenAsProperties();
     builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
   }
 }

   MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
       .resource(resource)
       .fetchSize(fetchSize)
       .timeout(timeout)
       .statementType(statementType)
       .keyGenerator(keyGenerator)
       .keyProperty(keyProperty)
       .keyColumn(keyColumn)
       .databaseId(databaseId)
       .lang(lang)
       .resultOrdered(resultOrdered)
       .resultSets(resultSets)
       .resultMaps(getStatementResultMaps(resultMap, resultType, id))
       .resultSetType(resultSetType)
       .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
       .useCache(valueOrDefault(useCache, isSelect))
       .cache(currentCache);

如上面的代码所示,在获取<cache>标签的所有属性信息后,调用MapperBuilderAssistant对象的userNewCache()方法创建二级缓存实例,然后通过MapperBuilderAssistant的currentCache属性保存二级缓存对象的引用。在调用MapperBuilderAssistant对象的addMappedStatement()方法创建MappedStatement对象时会将当前命名空间对应的二级缓存对象的引用添加到MappedStatement对象中,所以这就是需要配置<cache>的原因。

流程

![image-20211116235954488](https://img-blog.csdnimg.cn/img_convert/695760986f3bbfb9701b238c790e5892.png)



### mybatis二级缓存解决了什么问题

解决了一级缓存在不同session存在脏读的问题,但是分布式二级缓存也存在脏读。



![image-20211117000030143](https://img-blog.csdnimg.cn/img_convert/ae605add0674bdcb75bfb19684740c78.png)



### 总结

MyBatis一级缓存是SqlSession级别的缓存,默认就是开启的,而且无法关闭;二级缓存需要在MyBatis主配置文件中通过设置cacheEnabled参数值来开启。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1016294.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web服务(Web Service)

简介 Web服务&#xff08;Web Service&#xff09;是一种Web应用开发技术&#xff0c;用XML描述、发布、发现Web服务。它可以跨平台、进行分布式部署。 Web服务包含了一套标准&#xff0c;例如SOAP、WSDL、UDDI&#xff0c;定义了应用程序如何在Web上实现互操作。 Web服务的服…

第十九章、【Linux】开机流程、模块管理与Loader

19.1.1 开机流程一览 以个人计算机架设的 Linux 主机为例&#xff0c;当你按下电源按键后计算机硬件会主动的读取 BIOS 或 UEFI BIOS 来载入硬件信息及进行硬件系统的自我测试&#xff0c; 之后系统会主动的去读取第一个可开机的设备 &#xff08;由 BIOS 设置的&#xff09; …

线程安全问题的原因及解决方案

要想知道线程安全问题的原因及解决方案&#xff0c;首先得知道什么是线程安全&#xff0c;想给出一个线程安全的确切定义是复杂的&#xff0c;但我们可以这样认为&#xff1a;如果多线程环境下代码运行的结果是符合我们预期的&#xff0c;即在单线程环境应该的结果&#xff0c;…

基于 IntelliJ 的 IDE 将提供 Wayland 支持

导读对于使用 IntelliJ 开发环境的用户&#xff0c;JetBrains 一直致力于提供原生 Wayland 支持。 JetBrains 正在致力于为基于 IntelliJ 的 IDE 提供 Wayland 支持&#xff0c;以增强 Linux 桌面体验以及在 Windows Subsystem for Linux 下运行。 Wayland 支持功能尚未完成&…

Jmeter性能实战之分布式压测

分布式执行原理 1、JMeter分布式测试时&#xff0c;选择其中一台作为调度机(master)&#xff0c;其它机器作为执行机(slave)。 2、执行时&#xff0c;master会把脚本发送到每台slave上&#xff0c;slave 拿到脚本后就开始执行&#xff0c;slave执行时不需要启动GUI&#xff0…

专栏十:10X单细胞的聚类树绘图

经常在文章中看到对细胞群进行聚类,以证明两个cluster之间的相关性,这里总结两种绘制这种图的方式和代码,当然我觉得这些五颜六色的颜色可能是后期加的,本帖子只总结画树状图的方法 例一 文章Single-cell analyses implicate ascites in remodeling the ecosystems of pr…

zemax慧差与消慧差

基础设置&#xff1a; 该表面用于对系统的波前进行调制&#xff0c;得到想要的波前形状 通过理想透镜的光线在像空间聚焦&#xff0c;得到完美的球面波&#xff0c;经过调制可以模拟出任意的像差 这里的系数为泽尼克系数 1&#xff1a;平移 2&#xff1a;x轴倾斜 3&#x…

C盘简易无门槛清理指南

C盘在日常使用过程中会逐渐越来越少明明什么也没装&#xff0c;C盘空间却满了&#xff0c;导致最后爆满出现系统运行变慢&#xff0c;软件卡等现象。但随便删除一些东西&#xff0c;系统就崩溃了。本篇分析原因和介绍一些解决方法。 爆满原因主要分为四大类&#xff1a; 一&a…

浅谈C++|文件篇

引子&#xff1a; 程序运行时产生的数据都属于临时数据&#xff0c;程序一旦运行结束都会被释放通过文件可以将数据持久化。C中对文件操作需要包含头文件< fstream > 。 C提供了丰富的文件操作功能&#xff0c;你可以使用标准库中的fstream库来进行文件的读取、写入和定位…

Mobirise for Mac:轻松创建手机网站的手机网站建设软件

如果你是一位设计师或者开发人员&#xff0c;正在寻找一款强大的手机网站建设软件&#xff0c;那么Mobirise for Mac绝对值得你尝试。这个独特的应用程序将帮助你轻松创建优雅而实用的手机网站&#xff0c;而无需编写复杂的代码。 Mobirise for Mac的主要特点包括&#xff1a;…

Java ReentrantLock锁源码走读

目录 多线程例子程序&#xff1a;两个线程累加共享变量&#xff0c;结果正确非公平锁加锁&#xff08;即 lock.lock();&#xff09;过程非公平锁解锁&#xff08; lock.unlock();&#xff09;过程公平锁公平锁的加锁逻辑公平锁的释放锁逻辑 多线程例子程序&#xff1a;两个线程…

【JavaSE笔记】继承与多态(万字详解)

一、前言 在Java的核心概念中&#xff0c;继承和多态无疑是重要的一环。它们都是Java以及其他许多面向对象编程语言的基石&#xff0c;为我们提供了强大的工具来创建模块化&#xff0c;可重用和易于维护的代码。继承让我们可以创建新的类&#xff0c;通过继承现有类的属性和方…

关于单片机的分频定时器的记录

记录一内部时钟&#xff1a; 对于单片机的频率原来一直不太明白&#xff0c;现在在学习进行记录&#xff1a; 主频&#xff1a; 以一个72M的STM32单片机作为主频为例子&#xff0c;这个72M主频说得是一秒钟产生72000000&#xff08;七千两百万&#xff09;个脉冲或周期&…

POLARDB IMCI 白皮书 云原生HTAP 数据库系统 一 数据压缩打更新 (本篇有数据到列节点异步但不延迟的解释)...

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

使用ChatGPT和Blender绘制金色球的完整指南

简介&#xff1a; 在本篇博客中&#xff0c;我们将了解如何结合使用ChatGPT和Blender来创建一个金色的球体。ChatGPT是OpenAI开发的强大自然语言处理模型&#xff0c;而Blender则是一款流行的3D建模和渲染软件。通过结合这两个工具&#xff0c;您可以获得详细的指导&#xff0c…

【JavaEE】_JavaScript(WebAPI)

目录 1. DOM 1.1 DOM基本概念 1.2 DOM树 2. 选中页面元素 2.1 querySelector 2.2 querySelectorAll 3. 事件 3.1 基本概念 3.2 事件的三要素 3.3 示例 4.操作元素 4.1 获取/修改元素内容 4.2 获取/修改元素属性 4.3 获取/修改表单元素属性 4.3.1 value&#xf…

04条件构造器和常用接口

条件构造器和常用接口 wapper介绍 条件构造器的两个条件之间默认就是AND并列关系,如果需要或者的关系则需要调用构造器的or()方法 条件构造器类型作用Wrapper条件构造抽象类,最顶端父类AbstractWrapper生成SQL的where条件QueryWrapper封装查询或删除的条件UpdateWrapper封装修…

Python:Tornado框架之获取get和post的传参

一、获取get方式传参 import tornado.ioloop #导入tornado包 import tornado.web class MainHandle(tornado.web.RequestHandler):def get(self,id): #定义请求函数self.write("Hello %s!" %id)apptornado.web.Application([ #定义应用配置函数(r"/…

Python深度学习入门 - - 卷积神经网络学习笔记

文章目录 一、卷积神经网络简介二、卷积神经网络的数学原理1、卷积层2、池化层3、感受野 三、Python实战卷积神经网络1、LetNet-5网络2、Resnet 残差网络3、VGGNet 迁移学习 总结 一、卷积神经网络简介 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称…

Linux系统调试篇——核心转储调试(core dump)

文章目录 核心转储开启核心转储使用GDB调试core文件可能遇到的问题 本篇讲解Linux应用程序发生Segmentation fault段错误时&#xff0c;如何利用core dump文件定位错误。 核心转储 在 Linux 系统中&#xff0c;常将“主内存”称为核心(core)&#xff0c;而核心映像(core image…