1.quarkus实战之一:准备工作2.quarkus实战之二:应用的创建、构建、部署3.quarkus实战之三:开发模式(Development mode)4.quarkus实战之四:远程热部署5.quarkus实战之五:细说maven插件6.quarkus实战之六:配置7.quarkus实战之七:使用配置8.quarkus实战之八:profile9.quarkus依赖注入之一:创建bean10.quarkus依赖注入之二:bean的作用域11.quarkus依赖注入之三:用注解选择注入bean12.quarkus依赖注入之四:选择注入bean的高级手段13.quarkus依赖注入之五:拦截器(Interceptor)14.quarkus依赖注入之六:发布和消费事件15.quarkus依赖注入之七:生命周期回调16.quarkus依赖注入之八:装饰器(Decorator)17.quarkus依赖注入之九:bean读写锁18.quarkus依赖注入之十:学习和改变bean懒加载规则19.quarkus依赖注入之十一:拦截器高级特性上篇(属性设置和重复使用)20.quarkus依赖注入之十二:禁用类级别拦截器21.quarkus依赖注入之十三:其他重要知识点大串讲(终篇)22.quarkus数据库篇之二:无需数据库也能运行增删改查(dev模式)23.quarkus数据库篇之三:单应用同时操作多个数据库
24.quarkus数据库篇之四:本地缓存
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本文是《quarkus数据库篇》系列的第四篇,来实战一个非常有用的知识点:本地缓存
- 本地缓存可以省去远程查询数据库的操作,这就让查询性能有了显著提升,然而,对quarkus数据库本地缓存,我们不能抱太大希望,甚至在使用此功能时候要保持克制,不要用在重要场合,官方原文如下
- 个人的理解(请原谅我不入流的英文水平)
- quarkus的数据库本地缓存功能,还处于早期的、原始的、收到诸多限制的阶段
- 兼容性还没有做好(说不定quarkus一升级就会出现诸多问题)
- 将来可能会把更好的缓存方案集成进来(意思就是现在整个方案都不稳定)
- 实用的功能与摇摆不定的官方态度夹杂在一起,注定了本文不会展开细节,大家随我一道了解quarkus的缓存怎么用、效果如何,这就够了,主要分为以下四部分
- 新建一个子工程,写好未使用缓存的数据库查询代码
- 增加单个实体类的缓存,并验证效果
- 增加自定义SQL查询结果的缓存,并验证效果
- 增加一对多关联查询的缓存,并验证效果
- 这么水的内容,注定今天是一场轻松愉快的体验之旅(捂脸)
- 今天实战用的数据库依然是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-tutorials是个父工程,里面有多个module,本篇实战的module是basic-cache,如下图红框
开发-创建子工程
- 《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><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><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地址、账号密码等,如下图
- application.properties内容如下
quarkus.datasource.db-kind=postgresqlquarkus.hibernate-orm.log.sql=truequarkus.datasource.jdbc.max-size=8quarkus.datasource.jdbc.min-size=2quarkus.datasource.db-kind=postgresql quarkus.hibernate-orm.log.sql=true quarkus.datasource.jdbc.max-size=8 quarkus.datasource.jdbc.min-size=2quarkus.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=quarkusquarkus.datasource.password=123456quarkus.datasource.jdbc.url=jdbc:postgresql://192.168.50.43:15432/quarkus_testquarkus.hibernate-orm.database.generation=drop-and-createquarkus.hibernate-orm.sql-load-script=import.sqlquarkus.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.sqlquarkus.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);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);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);
- 配置完成,接下来把代码功能先想清楚,然后再编码
基本功能概述
- 接下来的功能会围绕两个表展开
- city:每一条记录是一个城市
- country:每一条记录是一个国家
- 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;}}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; } }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;@OneToManyList<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;}}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; } }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表的增删改查
@ApplicationScopedpublic class CityService {@InjectEntityManager entityManager;public City getSingle(Integer id) {return entityManager.find(City.class, id);}public List<City> get() {return entityManager.createNamedQuery("City.findAll", City.class).getResultList();}@Transactionalpublic void create(City fruit) {entityManager.persist(fruit);}@Transactionalpublic void update(Integer id, City fruit) {City entity = entityManager.find(City.class, id);if (null!=entity) {entity.setName(fruit.getName());}}@Transactionalpublic void delete(Integer id) {City entity = entityManager.getReference(City.class, id);if (null!=entity) {entityManager.remove(entity);}}}@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); } } }@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查询的,至于其他的操作如新增删除等,在本篇研究缓存时用不上就不写了
@ApplicationScopedpublic class CountyService {@InjectEntityManager entityManager;public Country getSingle(Integer id) {return entityManager.find(Country.class, id);}}@ApplicationScoped public class CountyService { @Inject EntityManager entityManager; public Country getSingle(Integer id) { return entityManager.find(Country.class, 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;@InjectCityService cityService;@InjectCountyService 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());// 记录总数应该等于已有记录数+1Assertions.assertEquals(numBeforeDelete + 1, cityService.get().size());}@Test@DisplayName("delete")@Order(5)public void testDelete() {// 先记删除前的总数int numBeforeDelete = cityService.get().size();// 删除testCreate方法中新增的记录,此记录的是第一次使用自增主键,所以id等于自增主键的起始idcityService.delete(ID_SEQUENCE_INIT_VALUE);// 记录数应该应该等于删除前的数量减一Assertions.assertEquals(numBeforeDelete-1, cityService.get().size());}}@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()); } }@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()); } }
- 运行单元测试,如下图,两个表的操作都正常,建表语句也符合预期
- 啥都准备好了,有请本地缓存闪亮登场
实体类缓存
- 先看不用缓存的时候,查询单个实体类的性能,增加一个单元测试方法testCacheEntity,用RepeatedTest让此方法执行一万次
@DisplayName("cacheEntity")@Order(6)@RepeatedTest(10000)public void testCacheEntity() {City city = cityService.getSingle(EXIST_FIRST_ID);// 判定非空Assertions.assertNotNull(city);}@DisplayName("cacheEntity") @Order(6) @RepeatedTest(10000) public void testCacheEntity() { City city = cityService.getSingle(EXIST_FIRST_ID); // 判定非空 Assertions.assertNotNull(city); }@DisplayName("cacheEntity") @Order(6) @RepeatedTest(10000) public void testCacheEntity() { City city = cityService.getSingle(EXIST_FIRST_ID); // 判定非空 Assertions.assertNotNull(city); }
- 点击下图红框中的绿色三角形按钮,会立即执行一万次testCacheEntity方法
- 执行完毕后,耗时统计如下图红框所示,47秒,单次查询耗时约为5毫秒左右,记住这两个数字
- 接下来是本篇的第一个关键:开启实体类缓存,其实很简单,如下图红框,增加Cacheable注解即可
- 再次运行单元测试的方法,如下图红框,总耗时从之前的47秒缩减到1秒多,黄框中有一些时间统计为空,这表示单次执行的时候耗时低于1毫秒
- 可见本地缓存的效果是显著的
SQL查询结果缓存
- 回顾city的entity类代码,如下图黄框,有一个自定义SQL
- 写一个单元测试方法,验证上述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());}@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()); }@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秒
- 然后是本篇的第二个重点:给SQL查询增加缓存,方法如下图红框,增加hints属性
- 为SQL添加了本地缓存后,再次执行同样的单元测试方法,效果如下图,本地缓存将SQL查询的耗时从1分零5秒缩短到1秒多钟
- 另外要注意的是,如果您的SQL是通过API执行的,而不是基于NamedQuery注解,那就要通过API来开启SQL缓存,示例如下
Query query = ...query.setHint("org.hibernate.cacheable", Boolean.TRUE);Query query = ... query.setHint("org.hibernate.cacheable", Boolean.TRUE);Query query = ... query.setHint("org.hibernate.cacheable", Boolean.TRUE);
一对多关联查询缓存
- country和city是一对多的关系,查询Country记录的时候,与其关联的city表记录也会被查询出来,填入Country对象的cities成员变量中
- 所以,是不是只要给实体类Country增加缓存注解,在查询Country的时候,其关联的City对象也会走本地缓存呢?
- 咱们来实际验证一下吧,先给Country类增加缓存注解,如下图红框
- 新增一个单元测试方法,查询一条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());}@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()); }@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秒,这显然是本地缓存没有生效的结果
- 接下来,就是本篇的第三个重点:设置一对多关联查询缓存,设置方法如下图红框所示
- 再次执行方法testCacheOne2Many,效果如下图红框所示,1秒多完成,缓存已生效
- 最后还要做件事情,就是完整的运行单元测试类CacheTest.java,如此做是为了验证这个场景:缓存开启的时候,如果做了写操作,接下来读取的也是最新的记录,而非缓存的之前的旧数据,即缓存失效功能,如下图,所有测试方法都顺利通过,总耗时3秒
重要提示
-
在使用本地缓存时有个问题需要注意:以city表为例,如果对city表的所有写操作都是通过当前应用完成的,那么使用本地缓存是没有问题的,如果除了basic-cache,还有另一个应用在修改city表,那么basic-cache中的缓存就不会失效(因为没人告诉它),这样从basic-cache中读取的数据因为是本地缓存,所以还是更新前的数据
-
至此,quarkus数据库本地缓存的现有方案,咱们已全部完成了,希望本文能给您一些参考,协助您提升应用性能
欢迎关注博客园:程序员欣宸
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END