quarkus数据库篇之四:本地缓存

这篇具有很好参考价值的文章主要介绍了quarkus数据库篇之四:本地缓存。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《quarkus数据库篇》系列的第四篇,来实战一个非常有用的知识点:本地缓存
  • 本地缓存可以省去远程查询数据库的操作,这就让查询性能有了显著提升,然而,对quarkus数据库本地缓存,我们不能抱太大希望,甚至在使用此功能时候要保持克制,不要用在重要场合,官方原文如下

quarkus数据库篇之四:本地缓存

  • 个人的理解(请原谅我不入流的英文水平)
  1. quarkus的数据库本地缓存功能,还处于早期的、原始的、收到诸多限制的阶段
  2. 兼容性还没有做好(说不定quarkus一升级就会出现诸多问题)
  3. 将来可能会把更好的缓存方案集成进来(意思就是现在整个方案都不稳定)
  • 实用的功能与摇摆不定的官方态度夹杂在一起,注定了本文不会展开细节,大家随我一道了解quarkus的缓存怎么用、效果如何,这就够了,主要分为以下四部分
  1. 新建一个子工程,写好未使用缓存的数据库查询代码
  2. 增加单个实体类的缓存,并验证效果
  3. 增加自定义SQL查询结果的缓存,并验证效果
  4. 增加一对多关联查询的缓存,并验证效果
  • 这么水的内容,注定今天是一场轻松愉快的体验之旅(捂脸)
  • 今天实战用的数据库依然是PostgreSQL,您可以根据自己情况自行调整

源码下载

  • 如果您想写代码,可以在我的GitHub仓库下载到完整源码,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos)
名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本次实战的源码在quarkus-tutorials文件夹下,如下图红框
    quarkus数据库篇之四:本地缓存
  • quarkus-tutorials是个父工程,里面有多个module,本篇实战的module是basic-cache,如下图红框
    quarkus数据库篇之四:本地缓存

开发-创建子工程

  • 《quarkus实战之一:准备工作》已创建了父工程,今天在此父工程下新增名为basic-cache的子工程,其pom与前文的工程区别不大,新增MySQL库,所有依赖如下
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <!-- JDBC库 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-agroal</artifactId>
        </dependency>
        <!-- hibernate库 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-hibernate-orm</artifactId>
        </dependency>
        <!-- postgresql库 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-postgresql</artifactId>
        </dependency>
        <!-- 单元测试库 -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

开发-配置文件

  • 为了满足多个profile的需要,配置文件继续使用application.properties和application-xxx.properties组合的方式,application.properties里存放公共配置,例如数据库类型,而application-xxx.properties里面是和各个profile环境有关的配置项,例如数据库IP地址、账号密码等,如下图
quarkus数据库篇之四:本地缓存
  • application.properties内容如下
quarkus.datasource.db-kind=postgresql
quarkus.hibernate-orm.log.sql=true
quarkus.datasource.jdbc.max-size=8
quarkus.datasource.jdbc.min-size=2
  • application-test.properties
quarkus.datasource.username=quarkus
quarkus.datasource.password=123456
quarkus.datasource.jdbc.url=jdbc:postgresql://192.168.50.43:15432/quarkus_test
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.sql-load-script=import.sql
  • 应用启动时加载数据的脚本import.sql
INSERT INTO city(id, name) VALUES (1, 'BeiJing');
INSERT INTO city(id, name) VALUES (2, 'ShangHai');
INSERT INTO city(id, name) VALUES (3, 'GuangZhou');

INSERT INTO country(id, name) VALUES (1, 'China');
INSERT INTO country_city(country_id, cities_id) VALUES (1, 1);
INSERT INTO country_city(country_id, cities_id) VALUES (1, 2);
INSERT INTO country_city(country_id, cities_id) VALUES (1, 3);
  • 配置完成,接下来把代码功能先想清楚,然后再编码

基本功能概述

  • 接下来的功能会围绕两个表展开
  1. city:每一条记录是一个城市
  2. country:每一条记录是一个国家
  3. country-cities:每一条记录是一个城市和国家的关系
  • 然后,咱们要写出city和country的增删改查代码,另外city和country是一对多的关系,这里涉及到关联查询
  • 最后,全部用单元测试来对比添加缓存前后的查询接口执行时间,以此验证缓存生效

开发-实体类

  • city表的实体类是City.java,和前面几篇文章中的实体类没啥区别,要注意的是有个名为City.findAll的自定义SQL查询,稍后会用来验证本地缓存是否对自动一个SQL有效
package com.bolingcavalry.db.entity;

import javax.persistence.*;

@Entity
@Table(name = "city")
@NamedQuery(name = "City.findAll", query = "SELECT c FROM City c ORDER BY c.name")
public class City {

    @Id
    @SequenceGenerator(name = "citySequence", sequenceName = "city_id_seq", allocationSize = 1, initialValue = 10)
    @GeneratedValue(generator = "citySequence")
    private Integer id;

    @Column(length = 40, unique = true)
    private String name;

    public City() {
    }

    public City(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • country表的实体类是Country.java,这里有一处要注意的地方,就是在我们的设计中,city和country表并不是通过字段关联的,而是一个额外的表记录了他们之间的关系,因此,成员变量citys并不对应country或者city表的某个字段,使用注解OneToMany后,quarkus的hibernate模块默认用country_cities表来记录city和country的关系,至于country_cities这个表名,来自quarkus的默认规则,如果您想用city或者country的某个字段来建立两表的关联,请参考javax.persistence.OneToMany源码的注释,里面有详细说明
package com.bolingcavalry.db.entity;

import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "country")
public class Country {

    @Id
    @SequenceGenerator(name = "countrySequence", sequenceName = "country_id_seq", allocationSize = 1, initialValue = 10)
    @GeneratedValue(generator = "countrySequence")
    private Integer id;

    @Column(length = 40, unique = true)
    private String name;

    @OneToMany
    List<City> cities;

    public Country() {
    }

    public Country(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<City> getCities() {
        return cities;
    }

    public void setCities(List<City> cities) {
        this.cities = cities;
    }
}
  • 两个实体类写完了,该写服务类了

开发-服务类

  • city表的增删改查
@ApplicationScoped
public class CityService {
    @Inject
    EntityManager entityManager;

    public City getSingle(Integer id) {
        return entityManager.find(City.class, id);
    }

    public List<City> get() {
        return entityManager.createNamedQuery("City.findAll", City.class)
                .getResultList();
    }

    @Transactional
    public void create(City fruit) {
        entityManager.persist(fruit);
    }

    @Transactional
    public void update(Integer id, City fruit) {
        City entity = entityManager.find(City.class, id);

        if (null!=entity) {
            entity.setName(fruit.getName());
        }
    }

    @Transactional
    public void delete(Integer id) {
        City entity = entityManager.getReference(City.class, id);

        if (null!=entity) {
            entityManager.remove(entity);
        }
    }
}
  • country表的增删改查,为了简化,只写一个按照id查询的,至于其他的操作如新增删除等,在本篇研究缓存时用不上就不写了
@ApplicationScoped
public class CountyService {
    @Inject
    EntityManager entityManager;

    public Country getSingle(Integer id) {
        return entityManager.find(Country.class, id);
    }
}
  • 应用代码已经写完了,接下来是验证基本功能的单元测试代码

开发-单元测试

  • 数据库数据被修改后,再次读取的时候,是读到最新的数据,还是之前缓存的旧数据呢?显然前者才是正确的,这就需要单元测试来保证正确性了
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CacheTest {

    /**
     * import.sql中导入的记录数量,这些是应用启动是导入的
     */
    private static final int EXIST_CITY_RECORDS_SIZE = 3;
    private static final int EXIST_COUNTRY_RECORDS_SIZE = 1;

    /**
     * 在City.java中,id字段的SequenceGenerator指定了initialValue等于10,
     * 表示自增ID从10开始
     */
    private static final int ID_SEQUENCE_INIT_VALUE = 10;

    /**
     * import.sql中,第一条记录的id
     */
    private static final int EXIST_FIRST_ID = 1;

    @Inject
    CityService cityService;

    @Inject
    CountyService countyService;



    @Test
    @DisplayName("list")
    @Order(1)
    public void testGet() {
        List<City> list = cityService.get();
        // 判定非空
        Assertions.assertNotNull(list);
        // import.sql中新增3条记录
        Assertions.assertEquals(EXIST_CITY_RECORDS_SIZE, list.size());
    }

    @Test
    @DisplayName("getSingle")
    @Order(2)
    public void testGetSingle() {
        City city = cityService.getSingle(EXIST_FIRST_ID);
        // 判定非空
        Assertions.assertNotNull(city);
        // import.sql中的第一条记录
        Assertions.assertEquals("BeiJing", city.getName());
    }

    @Test
    @DisplayName("update")
    @Order(3)
    public void testUpdate() {
        String newName = LocalDateTime.now().toString();

        cityService.update(EXIST_FIRST_ID, new City(newName));

        // 从数据库取出的对象,其名称应该等于修改的名称
        Assertions.assertEquals(newName, cityService.getSingle(EXIST_FIRST_ID).getName());
    }

    @Test
    @DisplayName("create")
    @Order(4)
    public void testCreate() {
        int numBeforeDelete = cityService.get().size();
        City city = new City("ShenZhen");
        cityService.create(city);

        // 由于是第一次新增,所以ID应该等于自增ID的起始值
        Assertions.assertEquals(ID_SEQUENCE_INIT_VALUE, city.getId());

        // 记录总数应该等于已有记录数+1
        Assertions.assertEquals(numBeforeDelete + 1, cityService.get().size());
    }

    @Test
    @DisplayName("delete")
    @Order(5)
    public void testDelete() {
        // 先记删除前的总数
        int numBeforeDelete = cityService.get().size();

        // 删除testCreate方法中新增的记录,此记录的是第一次使用自增主键,所以id等于自增主键的起始id
        cityService.delete(ID_SEQUENCE_INIT_VALUE);

        // 记录数应该应该等于删除前的数量减一
        Assertions.assertEquals(numBeforeDelete-1, cityService.get().size());
    }
}
  • 运行单元测试,如下图,两个表的操作都正常,建表语句也符合预期
quarkus数据库篇之四:本地缓存
  • 啥都准备好了,有请本地缓存闪亮登场

实体类缓存

  • 先看不用缓存的时候,查询单个实体类的性能,增加一个单元测试方法testCacheEntity,用RepeatedTest让此方法执行一万次
    @DisplayName("cacheEntity")
    @Order(6)
    @RepeatedTest(10000)
    public void testCacheEntity() {
        City city = cityService.getSingle(EXIST_FIRST_ID);
        // 判定非空
        Assertions.assertNotNull(city);
    }
  • 点击下图红框中的绿色三角形按钮,会立即执行一万次testCacheEntity方法
quarkus数据库篇之四:本地缓存
  • 执行完毕后,耗时统计如下图红框所示,47秒,单次查询耗时约为5毫秒左右,记住这两个数字
quarkus数据库篇之四:本地缓存
  • 接下来是本篇的第一个关键:开启实体类缓存,其实很简单,如下图红框,增加Cacheable注解即可
quarkus数据库篇之四:本地缓存
  • 再次运行单元测试的方法,如下图红框,总耗时从之前的47秒缩减到1秒多,黄框中有一些时间统计为空,这表示单次执行的时候耗时低于1毫秒
quarkus数据库篇之四:本地缓存
  • 可见本地缓存的效果是显著的

SQL查询结果缓存

  • 回顾city的entity类代码,如下图黄框,有一个自定义SQL
quarkus数据库篇之四:本地缓存
  • 写一个单元测试方法,验证上述SQL的实际性能
    @DisplayName("cacheSQL")
    @Order(7)
    @RepeatedTest(10000)
    public void testCacheSQL() {
        List<City> cities = cityService.get();
        // 判定非空
        Assertions.assertNotNull(cities);
        // import.sql中新增3条city记录
        Assertions.assertEquals(EXIST_CITY_RECORDS_SIZE, cities.size());
    }
  • 单元测试效果如下图,红框显示,没有使用缓存时,一万次自定义SQL查询需要1分钟零5秒
quarkus数据库篇之四:本地缓存
  • 然后是本篇的第二个重点:给SQL查询增加缓存,方法如下图红框,增加hints属性

quarkus数据库篇之四:本地缓存

  • 为SQL添加了本地缓存后,再次执行同样的单元测试方法,效果如下图,本地缓存将SQL查询的耗时从1分零5秒缩短到1秒多钟
quarkus数据库篇之四:本地缓存
  • 另外要注意的是,如果您的SQL是通过API执行的,而不是基于NamedQuery注解,那就要通过API来开启SQL缓存,示例如下
Query query = ...
query.setHint("org.hibernate.cacheable", Boolean.TRUE);

一对多关联查询缓存

  • country和city是一对多的关系,查询Country记录的时候,与其关联的city表记录也会被查询出来,填入Country对象的cities成员变量中
  • 所以,是不是只要给实体类Country增加缓存注解,在查询Country的时候,其关联的City对象也会走本地缓存呢?
  • 咱们来实际验证一下吧,先给Country类增加缓存注解,如下图红框
quarkus数据库篇之四:本地缓存
  • 新增一个单元测试方法,查询一条Country记录
    @DisplayName("cacheOne2Many")
    @Order(8)
    @RepeatedTest(10000)
    public void testCacheOne2Many() {
        Country country = countyService.getSingle(EXIST_FIRST_ID);
        // 判定非空
        Assertions.assertNotNull(country);
        // import.sql中新增3条city记录
        Assertions.assertEquals(EXIST_CITY_RECORDS_SIZE, country.getCities().size());
    }
  • 执行方法testCacheOne2Many,效果如下图红框所示,34秒,这显然是本地缓存没有生效的结果
quarkus数据库篇之四:本地缓存
  • 接下来,就是本篇的第三个重点:设置一对多关联查询缓存,设置方法如下图红框所示
quarkus数据库篇之四:本地缓存
  • 再次执行方法testCacheOne2Many,效果如下图红框所示,1秒多完成,缓存已生效
quarkus数据库篇之四:本地缓存
  • 最后还要做件事情,就是完整的运行单元测试类CacheTest.java,如此做是为了验证这个场景:缓存开启的时候,如果做了写操作,接下来读取的也是最新的记录,而非缓存的之前的旧数据,即缓存失效功能,如下图,所有测试方法都顺利通过,总耗时3秒
quarkus数据库篇之四:本地缓存

重要提示

  • 在使用本地缓存时有个问题需要注意:以city表为例,如果对city表的所有写操作都是通过当前应用完成的,那么使用本地缓存是没有问题的,如果除了basic-cache,还有另一个应用在修改city表,那么basic-cache中的缓存就不会失效(因为没人告诉它),这样从basic-cache中读取的数据因为是本地缓存,所以还是更新前的数据

  • 至此,quarkus数据库本地缓存的现有方案,咱们已全部完成了,希望本文能给您一些参考,协助您提升应用性能

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...文章来源地址https://www.toymoban.com/news/detail-653915.html

到了这里,关于quarkus数据库篇之四:本地缓存的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • quarkus实战之四:远程热部署

    将本地的改动极速同步到远程服务端,并自动生效,掌握此技能,开发调试会更高效 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本文是《quarkus实战》系列的第四篇,如标题所示,今天的任务是完成远程热部署实战 作为一名Java程序员,以下场

    2024年02月16日
    浏览(39)
  • 数据库:缓存穿透、缓存击穿、缓存雪崩

    请求一个不存在于缓存中的数据,由于该数据在后端数据库中也不存在,会导致请求不断地穿透缓存,并造成频繁地请求数据库。造成缓存穿透的主要原因是 黑客攻击 和 恶意请求 。 注意和缓存击穿的区别: 缓存穿透是指查询一个数据库一定不存在的数据 采用布隆过滤器,

    2024年02月14日
    浏览(34)
  • quarkus依赖注入之四:选择注入bean的高级手段

    这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本文是《quarkus依赖注入》系列的第四篇,在应用中,一个接口有多个实现是很常见的,那么依赖注入时,如果类型是接口,如何准确选择实现呢?前文介绍了五种注解,用于通过配置项、profile等手

    2024年02月14日
    浏览(41)
  • redis实战-缓存数据&解决缓存与数据库数据一致性

    缓存( Cache),就是数据交换的 缓冲区 ,俗称的缓存就是 缓冲区内的数据 ,一般从数据库中获取,存储于本地代码。防止过高的数据访问猛冲系统,导致其操作线程无法及时处理信息而瘫痪,这在实际开发中对企业讲,对产品口碑,用户评价都是致命的;所以企业非常重视缓存技术,

    2024年02月12日
    浏览(55)
  • 缓存更新策略,先更新数据库还是缓存呢?

    学了这么多,相信大家对缓存更新的策略都已经有了清晰的认识。最后稍稍总结一下。 缓存更新的策略主要分为三种: Cache aside Cache aside 也就是 旁路缓存 ,是比较常用的缓存策略。 (1) 读请求 常见流程 应用首先会判断缓存是否有该数据,缓存命中直接返回数据,缓存未

    2024年02月12日
    浏览(42)
  • Redis缓存数据库

    目录 一、概述 1、Redis  2、Redis的安装 Redis Windows环境设置 3、String: 字符串 3.1、字符串 3.2、数值 3.3、bitmap 4、Hash: 散列 5、List: 列表 6、Set: 集合 7、Sorted Set: 有序集合 常识: 磁盘:1.寻址:ms(毫秒)2.带宽:MB/s 内存:1.寻址:ns    (纳秒) 2.带宽:GB/s 秒--毫秒--微妙--纳秒

    2024年02月04日
    浏览(62)
  • Redis缓存数据库(四)

    目录 一、概述 1、Redis Sentinel 1.1、docker配置Redis Sentinel环境 2、Redis存储方案 2.1、哈希链 2.2、哈希环 3、Redis分区(Partitioning)  4、Redis面试题 Redis Sentinel为Redis提供了 高可用解决方案 。实际上这意味着使用Sentinel可以部署一套Redis, 在没有人为干预的情况下去应付各种各样的失

    2024年02月05日
    浏览(55)
  • 前端数据库与缓存实践

    前端数据库与缓存技术在现代网络应用中发挥着越来越重要的作用。随着前端技术的不断发展,前端数据库和缓存技术也在不断发展和进化。这篇文章将从以下几个方面进行阐述: 背景介绍 核心概念与联系 核心算法原理和具体操作步骤以及数学模型公式详细讲解 具体代码实

    2024年02月21日
    浏览(37)
  • 缓存和数据库一致性

    项目的难点是如何保证缓存和数据库的一致性。无论我们是先更新数据库,后更新缓存还是先更新数据库,然后删除缓存,在并发场景之下,仍然会存在数据不一致的情况(也存在删除失败的情况,删除失败可以使用异步重试解决)。有一种解决方法是延迟双删的策略,先删

    2024年01月17日
    浏览(45)
  • 如何保证缓存和数据库的数据一致性

    若数据库更新成功,删除缓存操作失败,则此后读到的都是缓存中过期的数据,造成不一致问题。 同删除缓存策略一样,若数据库更新成功缓存更新失败则会造成数据不一致问题。 若缓存更新成功数据库更新失败, 则此后读到的都是未持久化的数据。因为缓存中的数据是易

    2023年04月19日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包