CaffeineCache基本使用 & SpringBoot集成缓存

2023-11-06

平常开发中我们经常会使用到缓存,比如对于一些不常更新的数据却需要经常的访问或者计算,为这些热点数据加缓存可以有效减少服务器的性能损失和资源浪费。
本地缓存相比Redis缓存以及其他存储避免了网络IO的开销,它不需要发送redis命令,直接在本地jvm进程中操作缓存数据,而且基于内存的读写效率很高,所以在需要的时候合理使用本地缓存可以有效提高系统的吞吐量。CaffeineCache是非常优秀的开源本地缓存框架。本篇不分析其原理,只关注其简单用法。

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>2.6.2</version>
</dependency>

一、常用API

CaffeineCache 有非常简易的API,可以通过如下方式快速构建一个本地缓存。

LoadingCache<String, Object> cache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.SECONDS)
                .expireAfterAccess(1, TimeUnit.SECONDS)
                .maximumSize(10)
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String s)  {
                        return s + "loadValue";
                    }
                });

缓存的常用API如下:

public interface Cache<K, V> {
    //取值,如果不存在则返回null
    @Nullable
    V getIfPresent(@Nonnull Object key);

    //取值,如果不存在则执行函数并将执行结果缓存
    @Nullable
    V get(@Nonnull K key, @Nonnull Function<? super K, ? extends V> var2);

    //存值
    void put(@Nonnull K key, @Nonnull V value);

    //批量存值,接受一个map类型的参数,将map中的元素缓存起来
    void putAll(@Nonnull Map<? extends K, ? extends V> map);

    //移除缓存项
    void invalidate(@Nonnull Object key);

    //接受一个可迭代的参数进行批量移除
    void invalidateAll(@Nonnull Iterable<?> var1);

    //清空缓存
    void invalidateAll();
    
    //获取当前缓存项的数量(map中的条目数)
    @Nonnegative
    long estimatedSize();
    
    //缓存的map视图
    @Nonnull
    ConcurrentMap<K, V> asMap();
    //……
}
public interface LoadingCache<K, V> extends Cache<K, V> {
	@Nullable
    V get(@Nonnull K key);
    @Nonnull
    Map<K, V> getAll(@Nonnull Iterable<? extends K> var1);
    void refresh(@Nonnull K var1);
}

可以看到LoadingCache继承了Cache接口,扩展了三个API,这三个API描述如下:

1.get

  1. 获取key对应的value,在必要时通过CacheLoader加载数据。
  2. 加载的 非null数据会缓存 到Cache中。
  3. 如果同时已有其他线程在加载key对应的数据,当前线程阻塞waits,等待那个线程加载并返回那个加载到的数据。

2.getAll

  1. 获取所有key对应的value,在必要时通过CacheLoader加载数据。
  2. 加载的非null数据会缓存到Cache中。
  3. 如果同时已有其他线程在加载某个key对应的数据,当前线程阻塞waits,等待那个线程加载并返回那个加载到的数据。

3.refresh

  1. 异步刷新key对应的缓存数据
  2. 在新数据加载成功前,旧数据仍可用(除非过期或被驱逐)
  3. 新数据加载成功后,replace旧数据
  4. 如果数据加载时异常,会被swallowed。
  5. 如果数据加载的value为null,key对应的Entry会从缓存中移除。

此处参考自:http://events.jianshu.io/p/881c6f716850

二、缓存回收(清除):

1. 显式回收

  1. invalidate(@Nonnull Object key);
  2. invalidateAll(@Nonnull Iterable<?> var1);
  3. invalidateAll()

2. 隐式回收

2.1 基于容量

maximumSize(long):通过指定缓存的最大条目数,当缓存的条目超过最大大小,则按照一定的淘汰算法将旧数据移除。

LoadingCache<String, Object> cache = Caffeine.newBuilder().maximumSize(10)//……

Caffeine采用W-TinyLFU算法作为缓存淘汰算法,这种算法结合了LRU和LFU。算法描述可参考https://my.oschina.net/manmao/blog/603253https://blog.csdn.net/l_dongyang/article/details/108583476

2.2 基于时间

  1. TTL(Time To Live): 通过expireAfterWrite(long, TimeUnit)指定当缓存项被写入(创建/覆盖)多久后被回收。
  2. TTI(Time To Idle): 通过expireAfterAccess(long, TimeUnit)指定缓存项多久没有被访问(读/写)而将其回收。
LoadingCache<String, Object> cache = Caffeine.newBuilder().weakValues()
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .expireAfterAccess(5, TimeUnit.SECONDS)//……

特别需要注意的是:设置过期时间之后,到达指定之间后缓存项并不会被马上移除,它没有启动定时器,而是依赖下次对cache的操作(读写操作),当下次对这个cache对象进行读写操作时,它会利用一个线程将cache中的已失效缓存项从内存中清除。expireAfterWrite和expireAfterAccess如果同时设置,取二者指定的最小时间。

  1. put操作:put当前的缓存项,并启动一个线程去后台执行过期缓存项的移除任务。
  2. get操作:如果发现cache中有过期缓存项,则启动一个线程去后台执行这些缓存项的移除任务,如果get的正好是过期的缓存项,则当前线程会马上执行load方法,直到load方法加载完毕,get方法返回。
  3. getIfPresent操作:如果发现cache中有过期缓存项,则启动一个线程去后台执行这些缓存项的移除任务,如果getIfPresent的正好是过期的缓存项,则当前线程马上返回null。

2.3 基于引用

  1. 软引用:如果一个对象是软引用,那么当JVM堆内存不足时,垃圾回收器可以回收这些对象。软引用适合用来做缓存,从而当JVM堆内存不足时,可以回收这些对象腾出一些空间供强引用对象使用,从而避免OOM。框架提供 softValues() 供我们在创建缓存时调用,可以将值包装成软引用。

  2. 弱引用:当垃圾回收器回收内存时,如果发现弱引用,则将立即回收它。相对于软引用有更短的生命周期。框架提供weakKeys()和weakValues()两个方法供我们在创建缓存时调用,可以将键/值包装成弱引用,但是不要使用weakKeys(),这可能导致缓存无法命中。

LoadingCache<String, Object> cache = Caffeine.newBuilder().weakValues()
//……

2.4 基于权重

通过权重来计算,每个缓存项都有不同的权重,总权重到达最高时按照淘汰算法进行回收。
框架提供maximumWeight()方法指定最大的权重阈值,通过weigher()方法指定缓存项所占权重。

LoadingCache<String, Object> cache = Caffeine.newBuilder()
    .maximumWeight(10)
    .weigher(new Weigher<Object, Object>() {
                @Override
                public int weigh(Object key, Object value) {
                    //TODO
                    return 0;
                }
    }) //……

三、刷新缓存(reload)

LoadingCache<String, Object> cache = Caffeine.newBuilder()
                //方式二
                .refreshAfterWrite(3,TimeUnit.SECONDS)
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        return key + "loadValue";
                    }

                    @Override
                    public Object reload(String key, Object oldValue) throws Exception {
                        //该方法默认返回 return CacheLoader.super.reload(key, oldValue)---里面直接调用了this.load(key)方法
                        return key + "reloadValue";
                    }
                });
    //方式一
   cache.refresh("key")      
  1. 方式一:通过cache.refresh(“key”)显示刷新,如果key已经被缓存,则执行reload方法重新加载;如果key没有被缓存,则执行load方法加载。

  2. 方式二:通过refreshAfterWrite方法定时刷新,该方法并不是通过额外的线程启动定时任务去刷新,而是依赖查询请求,当我们查询(get/getIfPresent)某个缓存时它会去比对当前时间与该缓存项被写入(创建/覆盖)的时间,如果超过了所设置的时间值,则执行reload方法刷新。

注意:reload方法是通过启动后台线程异步执行的,当前线程依然会立刻返回之前的旧值。当后台线程刷新完成并成功取代之前的旧值后再次获取才是刷新后的新值。

四、监听器

框架提供了缓存项被移除的事件监听机制,通过这种机制我们可以得知哪些缓存项被移除了,因为什么原因被移除的。当然也可以在key被移除时做一些其他操作。

LoadingCache<String, Object> cache = Caffeine.newBuilder()
                .removalListener(new RemovalListener<Object, Object>() {
                    @SneakyThrows
                    @Override
                    public void onRemoval(Object key, Object value, RemovalCause removalCause) {
                        System.out.println(key + ":" + value + "被移除了,原因是:" + removalCause);
                    }
                })//……
  1. SIZE:由于超过缓存最大数量被移除
  2. EXPLICIT: 被显式清除的
  3. EXPIRED:超过过期时间
  4. REPLACED:被取代(比如同一个key连续put两次,第一次的缓存项就是被取代的,还有被刷新等)
  5. COLLECTED:被垃圾收集器收集导致的。

五、外部存储

框架提供CacheWriter接口供我们在写入缓存项和删除缓存项时进行扩展,可以用于操作外部存储。

LoadingCache<String, Object> cache = Caffeine.newBuilder()
                .writer(new CacheWriter<Object, Object>() {
                    //写入缓存后会调用此方法
                    @Override
                    public void write(@NonNull Object key, @NonNull Object value) {
                        //这里可以将缓存的数据写入外部存储
                    }

                    //删除缓存项后会调用此方法
                    @Override
                    public void delete(@NonNull Object key, @Nullable Object value, @NonNull RemovalCause removalCause) {
                        //这里可以删除外部存储
                    }
                })//……

六、SpringBoot集成缓存

上述是Caffeine中的一些常用API使用,在SpringBoot项目中可以直接定义一个个LoadingCache的Bean,然后使用原生API操作缓存。除此之外,也可以使用SpringBoot提供的缓存操作的相关注解,可以简化开发(前提是需要配置**@EnableCaching**开启缓存)。

1. 几个常用的缓存注解

1.1 @Cacheable(最重要的注解)

该注解一般标注在方法上,在方法执行前会先查询缓存,如果命中,则不再目标执行方法,如果没有命中缓存,则目标执行方法,并将方法的返回结果缓存(这里就像上面CaffeineCache的CacheLoad一样,没有命中则执行load方法去加载)。该注解的属性如下:

public @interface Cacheable {
    @AliasFor("cacheNames")
    String[] value() default {};

    //  注定缓存的名字,比如员工缓存可以指定为empCache,学生缓存可以指定为stuCache。这些Cache都被CacheManager管理
    @AliasFor("value")
    String[] cacheNames() default {};

    //指定缓存的key,默认使用的是方法的参数值。支持SpEL写法
    String key() default "";

    //指定缓存key的生成器来生成key。若同时指定了key属性,则使用key属性指定的key
    String keyGenerator() default "";

    //指定缓存管理器
    String cacheManager() default "";

    //指定缓存解析器(与cacheManager功能一样)
    String cacheResolver() default "";

    //指定符合条件的情况下才缓存,支持SpEL写法
    String condition() default "";

    //否定缓存。当unless的条件为true,则不会被缓存。可以对结果进行判断,如unless="#result == null"(表示结果为null则不缓存),支持SpEl写法。
    String unless() default "";

    //是否需要异步去缓存
    boolean sync() default false;
}

CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。
上述部分属性可以通过SpEL指定,Spring Cache提供了一些供我们使用的SpEL上下文数据,如下:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当 方法执行后 的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

使用如下:

@Cacheable(cacheNames = {"person"}, key = "#tid", cacheManager = "cacheManager")
public Person getPerson(String tid){
	return DBData.selectPerson(tid);
}

上面通过key属性指定了key,除此之外还可以实现KeyGenerator接口来指定key的生成规则。

@FunctionalInterface
public interface KeyGenerator {
	/**
	 * target:目标方法的对象实例
	 * method:目标方法
	 * args:目标方法的执行参数
	 * @return:key
     */
    Object generate(Object target, Method method, Object... args);
}

1.2 @CacheEvict

该注解一般标注在方法上,作用是清除缓存,当方法内对数据进行更新、删除操作的时候,那就需要用到它来清除缓存。否则将拿到旧数据。

public @interface CacheEvict {
    @AliasFor("cacheNames")
    String[] value() default {};
	//指定缓存
    @AliasFor("value")
    String[] cacheNames() default {};
    // 指定要清除的key
    String key() default "";
    //设置为true则表示清空缓存
    boolean allEntries() default false;
    //缓存的清除是否在方法执行之前,默认在方法执行成功之后执行
    boolean beforeInvocation() default false;
   //……
}

1.3 @CacheConfig

该注解只能标注在类上,用于指定缓存属性的全局配置。像上面@Cacheable、@CacheEvict注解如果每次使用都需要指定cacheNames属性的配置,而且这些cacheNames都一样,那就可以使用@CacheConfig将公共的配置提取到类上,改类的方法就默认以全局配置为主,不需要二次配置。

public @interface CacheConfig {
    String[] cacheNames() default {};
    String keyGenerator() default "";
    String cacheManager() default "";
    String cacheResolver() default "";
}

1.4 @CachePut

该注解一般标注在方法上,作用是会将被注解方法的返回值进行缓存。利用该注解也可以做缓存的更新。

1.5 @Caching

该注解是上面三种注解的组合注解,如果缓存规则比较复杂,可以使用它。

public @interface Caching {
    Cacheable[] cacheable() default {};
    CachePut[] put() default {};
    CacheEvict[] evict() default {};
}

2. 集成Caffeine作为缓存框架

SpringBoot默认使用ConcurrentMapCache,内部使用ConcurrentHashMap作缓存。当我们引入Caffeine的依赖之后,SpringBoot中的CaffeineCacheConfiguration就会生效,就会自动装配CaffeineCacheManager,则内部使用的缓存框架就变成Caffeine。默认的CaffeineCacheManager如下:

@Bean
CaffeineCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers customizers, ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec, ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
	CaffeineCacheManager cacheManager = this.createCacheManager(cacheProperties, caffeine, caffeineSpec, cacheLoader);
	List<String> cacheNames = cacheProperties.getCacheNames();
	if (!CollectionUtils.isEmpty(cacheNames)) {
		cacheManager.setCacheNames(cacheNames);
	}
	return (CaffeineCacheManager)customizers.customize(cacheManager);
}

private CaffeineCacheManager createCacheManager(CacheProperties cacheProperties, ObjectProvider<Caffeine<Object, Object>> caffeine, ObjectProvider<CaffeineSpec> caffeineSpec, ObjectProvider<CacheLoader<Object, Object>> cacheLoader) {
	CaffeineCacheManager cacheManager = new CaffeineCacheManager();
	this.setCacheBuilder(cacheProperties, (CaffeineSpec)caffeineSpec.getIfAvailable(), (Caffeine)caffeine.getIfAvailable(), cacheManager);
	cacheLoader.ifAvailable(cacheManager::setCacheLoader);
	return cacheManager;
}

当然我们可以自己配置一个缓存管理器的Bean,那么自动配置类CaffeineCacheConfiguration就不会生效,让其内部维护CaffeineCache即可。此处配置CaffeineCacheManager的Bean,如下:

@EnableCaching
@Configuration
public class CacheConfiguration {
    /**
     * 配置缓存管理器
     */
    @Bean("caffeineCacheManager")
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .expireAfterAccess(20, TimeUnit.SECONDS)
                .initialCapacity(10)
                .weakValues()
                .maximumSize(100));
		cacheManager.setCacheLoader(new CacheLoader<Object, Object>() {
            @Override
            public Object load(Object o) throws Exception {
                //从redis中读取缓存项,如果redis中没有则返回null,null不会被缓存。
                // 那么就会执行@Cacheable注解的方法,从DB中读
                return null;
            }
        });
        return cacheManager;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CaffeineCache基本使用 & SpringBoot集成缓存 的相关文章

  • 理解 Spring AOP [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我正在使用 Spring 3 0 框架 但仍然是新手 谁能通俗地解释一下什么是AOP编程 一个简短的例子肯定会有帮助 Spring 如
  • 导入的项目抛出 java.lang.ClassNotFoundException

    所以我将一个现有的项目导入到 eclipse 中 但让它工作时遇到了很多麻烦 该项目与其他三个图书馆项目一起提供 谷歌播放服务库 捕获活动 肖像二维码 As soon as I imported the project I went und
  • 使用 Atomikos 进行两阶段提交 (2PC) 配置

    我正在创建一个示例应用程序来测试两阶段提交 2PC 我从互联网上获取了此处使用的代码位 我使用 Spring Hibernate 和 Atomikos 并以 MySQL 作为后端 我正在使用两个数据库 并故意使对第二个数据库的调用失败 以检
  • java中使用多个分隔符分割字符串

    我正在研究一种数据挖掘算法 我需要使用多个单词来标记字符串 我有一个单独的文件 其中包含所有停用词 我需要做的是通过任何作为分隔符的单词 停用词 来标记输入字符串 例如 如果文件包含停用词 a is and of that 输入字符串变为
  • 如何在 JavaFX 中设置滚动窗格的单位增量?

    The 滚动条 http docs oracle com javafx 2 api javafx scene control ScrollBar htmlJavaFX 中的类包含一个用于设置单位增量的属性 这就是我所追求的 但是我找不到如何
  • 如何解决 javax.net.ssl.SSLHandshakeException 错误?

    我通过 VPN 连接来设置库存 API 来获取产品列表 效果很好 一旦我从网络服务获得结果并绑定到用户界面 而且我将 PayPal 与我的应用程序集成在一起 以便在我拨打电话付款时进行快速结帐 但我遇到了此错误 我使用 servlet 进行
  • 仅在文件下载完成后设置 cookie。

    我有一个场景 我想告诉用户下载完成并提示关闭按钮 为此 我使用 jquery 插件来连续监视 cookie 以了解下载何时完成 我的问题是我想设置这个cookie fileDownload true and path 下载完成后立即进行 为
  • 执行 POST 请求时 Spring Boot 端点 403 OPTIONS

    我正在使用 Spring 运行一个服务 当我的 Angular 前端尝试发出 POST 请求时 会收到带有请求方法 选项的 403 错误 Spring 服务和 Angular 应用程序都在我的机器上本地运行 我尝试使用 Chrome 插件切
  • Android O - 通知通道和NotificationCompat

    我无法改变这种感觉 Android 开发人员再次提出了一些新东西 却让每个人都对他们如何看待该功能的使用一无所知 我说的是 Android O 中的通知通道 多年来 我一直使用兼容性支持库来避免处理特定平台的细节 即 Notificatio
  • Java KeyListener:按下两个键时如何执行操作?

    请看下面的代码 import java awt event import javax swing import java awt public class KeyCheck extends JFrame private JButton ch
  • Java中对象类的继承

    当我读java书时 我遇到了 每个类都扩展类 Object 但是如果想要 B 类扩展 A 类 但是 B 类现在将具有多重继承 一个来自 Object 类 一个来自 A 类 如何解决冲突 谁能解释一下吗 它是多级继承 而不是多重 class
  • 有人让动物嗅探器插件工作吗?

    maven animal sniffer 插件承诺告诉我我的代码是否有任何对 Java 1 6 或更高版本 API 的引用 这对于我们这些在 MacOSX Snow Leopard 只有官方 1 6 上开发但需要交付到 1 5 环境的人来说
  • 从多个文本文件读取数据[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我是Java编程新手 我正在尝试打印
  • PDFBox - 无障碍 PDF - 如何检查 PDF 标签是否具有符合无障碍指南的属性

    需要检查 PDF 标签是否具有符合辅助功能指南的属性 例子 H1 验证 PDF 中是否存在 H1 图像 图形标签 验证图像 图形是否具有 Alt 文本 语言 验证语言属性是否已设置 以便屏幕阅读器能够正确阅读 对于西班牙语和英语文档 应更新
  • 为数组生成随机索引

    我知道对于普通整数来说这是这样 但是有索引这样的东西吗 Random dice new Random int n dice nextInt 6 System out println n 你是什 么意思 数组索引是普通数字 所以你可以轻松地做
  • ACTION_MEDIA_BUTTON 的广播接收器不起作用

    我正在为 Android 操作系统版本 4 0 3 ICS 编写 Android 应用程序 问题是我没有从 BroadcastReceiver 的 onReceive 方法中的 Log d 获得输出 这意味着我的应用程序没有正确处理广播 我
  • 将字符串从代码页 1252 转换为 1250

    我怎样才能转换一个String将代码页 1252 中的字符解码为String在代码页 1250 中解码 例如 String str1252 String str1250 convert str1252 System out print st
  • 在 Android 中创建硬链接和符号链接

    我正在创建一个应用程序 我想在其中使用 Android 外部内存文件系统中的硬链接和符号链接 我尝试过使用命令 Os link oldpath newpath Os link oldpath newpath 但是 当我尝试这样做时 我收到此
  • 为什么 pagefactory 类在从另一个类初始化时返回 null

    在我的测试课上 我有DesiredCapabilities为 Appium 测试设置 在该课程中 我初始化了 BasePage 课程pagefactory元素 当我运行测试时 它按预期工作 现在 我尝试将 DesiredCapability
  • 从 HashMap 中查找对象键

    我有一个 HashMap 其键作为我自己的对象 键作为 String 的 ArrayList 有没有一种方法可以从映射中获取与另一个对象相同的关键对象 而无需迭代映射 请注意 我的对象已经实现了 equals 和 hashcode 并且它只

随机推荐

  • JAVA中Scanner类中,next()与nextLine()的异同

    在运用Scanner做语句解析的时候在next 与nextLine 之间小小的困惑了一下 下面是我的调试分享 next public static void main String args Scanner scanner new Scan
  • 抖音新秒注销,无需等7天的方法,机不可失马上和谐

    抖音注销账号新方法 快速安全 不再需要等待7天 立即与谐同步 随着社交媒体的普及和用户需求的变化 有些人可能希望注销自己在抖音上的账号 然而 传统的注销流程通常需要等待7天的冷静期 让很多用户感到不便和焦虑 今天 我将为大家介绍一种全新的方
  • 升级到Android Studio 3.2.1 ,报 org.gradle.internal.exceptions.LocationAwareException

    最近收到AS版本的推送就果断更新了 更新以后 打开自己最近的项目 报如下错误 No route to host connect failed Caused by org gradle internal exceptions Location
  • Python 我编码遇到的错误

    ValueError invalid literal for int with base 10 0 000 Traceback most recent call last File
  • Hadoop环境搭建(主机名、Ip地址、映射及网络配置)

    一 在安装的虚拟机上修改主机名地址 1 显示当前主机名命令 hostname 2 修改主机名命令 方法 输入 vi etc hostname 方法 输入 hostnamectl set hostname 进入后在编辑模式下 删除原来的主机名
  • java操作excel获取每列的信息并按照学号-姓名格式创建文件夹

    1 导入Maven依赖
  • 数据库连接工具类

    数据库连接 概述 一 jdbc 实验环境搭建 二 Druid连接数据库 实验环境搭建 三 Maven连接数据库 实验环境搭建 四 mybatis连接数据库 实验环境搭建 五 Spring连接数据库 六 SSM 概述 什么是JDBC Java
  • 前端实现Jest单元测试

    介绍 最近在学一些关于工程化的内容 里面正好提到了jest单元测试 首先简单理解一下什么是单元测试 举个例子 小明同学偶然发现海海同学做的组件库不错 想学习一下 于是就拉了代码 不过在看代码的过程中发现有的代码有更优性能的方法 但是呢他自己
  • ctf.show web web1-web10

    ctf show web web1 web10笔记 记录一些web的知识点 本人刚开始学习web很多资料都是借鉴大佬的 许多复现的过程都是大同小异的 写的菜了请大佬们下手轻点 web1 一道简单的入门题 打开环境发现只有一串英文 flag在
  • 唯一索引比普通索引快吗?运行原理是什么?

    推荐阅读 项目实战 AI文本 OCR识别最佳实践 AI Gamma一键生成PPT工具直达链接 玩转cloud Studio 在线编码神器 玩转 GPU AI绘画 AI讲话 翻译 GPU点亮AI想象空间 资源分享 史上最全文档AI绘画stab
  • 从源码角度深入分析iScroll中的scrollToElement方法

    问题1 官方解释 scrollToElement el time offsetX offsetY easing You re gonna like this Sit tight The only mandatory parameter is
  • cocos2d-x-2.2.4 (四) 将MyGame在Android上跑起来

    继续上一篇 我接着在MyGame工程干活 这次要将MyGame运行在Android设备上 要将cocos2dx的项目在Android上跑起来需要NDK和eclipse NDK用来编译cocos2dx的cpp文件 将其打包成动态库文件 例如l
  • 数字信号处理技术(二)变分模态分解(VMD)-Python代码

    本文仅对变分模态分解 VMD 的原理简单介绍和重点介绍模型的应用 1 VMD原理 变分模态分解 VMD 的原理在此不做详细介绍 推荐两个不错的解释参考连接 变分模态分解原理步骤 和VMD算法的介绍 官方源码 2 VMD应用实战 2 1 简介
  • AngularJS系列之JavaScript函数和对象

    转载请注明来源 http blog csdn net caoshiying viewmode contents 这篇文章针对的是有2年以上编程经验的朋友们参考的 作参考资料之用 不从基础讲起 在ES6之前 JavaScript没有class
  • Mybatis 入门

    1 Mybatis 项目构建 新建数据库 CREATE DATABASE mybatis USE mybatis DROP TABLE IF EXISTS user CREATE TABLE user id INT 20 NOT NULL
  • 多元共进|拓宽知识边界,持续增长技能

    在开放且迅速迭代的技术生态中 开发者面临着无限的机遇 与此同时 开发者的知识储备和技能水平也被赋予了更高的期待 2023 Google 开发者大会与开发者们共享了技术新知 也同步提供持续更新的学习资源 包括可以上手实践的课程和练习 加深开发
  • python+opencv+numpy入门

    一 简介 python是一门编程语言 由于其可以调用很多科学计算包 如numpy scipy matplotlib等而功能强大 numpy是python可调用的科学计算包 主要用于矩阵运算 它是python的数值计算扩展 这种工具可用来存储
  • STM32软件加密

    摘要 知识产权的保护 如何让自已辛勤的劳动成果不被别人抄袭 采用有效的手段对IC加密是值得每一个设计者关注的问题 当然 有人说 没有解不了密的IC 的确 解密是一项技术 只要有人类在不断的研究 它就有破解的一天 但是加密后的IC会增加破解的
  • 【数据库实验报告】关于SQL Server 简单的使用

    关于SQL Server 简单的使用 1 登陆SSMS 首先登陆 之前开启了sa账户 现在使用sa账户登陆 2 创建数据库 右键数据库 点击新建数据 输入数据库名称 然后确定 这个时候 已经新建了一个数据库了 现在在左侧管理器中 就可以看到
  • CaffeineCache基本使用 & SpringBoot集成缓存

    文章目录 一 常用API 1 get 2 getAll 3 refresh 二 缓存回收 清除 1 显式回收 2 隐式回收 2 1 基于容量 2 2 基于时间 2 3 基于引用 2 4 基于权重 三 刷新缓存 reload 四 监听器 五