Caching boleh meningkatkan prestasi dan kestabilan sistem dengan berkesan dengan menyimpan data yang kerap diakses dalam ingatan, mengurangkan tekanan pada sumber data asas seperti pangkalan data. Saya rasa semua orang telah menggunakannya lebih kurang dalam projek mereka, dan projek kami tidak terkecuali Namun, semasa saya menyemak kod syarikat baru-baru ini, tulisannya sangat bodoh dan rendah adalah seperti berikut:
public User getById(String id) { User user = cache.getUser(); if(user != null) { return user; } // 从数据库获取 user = loadFromDB(id); cahce.put(id, user); return user; }
Malah, Spring Boot menyediakan abstraksi caching yang berkuasa yang memudahkan untuk menambah caching pada aplikasi anda. Artikel ini akan membincangkan tentang cara menggunakan anotasi cache berbeza yang disediakan oleh Spring untuk melaksanakan amalan terbaik untuk caching.
Kebanyakan projek sekarang ialah projek SpringBoot Kami boleh menambah anotasi @EnableCaching
pada kelas permulaan untuk mendayakan fungsi caching.
@SpringBootApplication @EnableCaching public class SpringCacheApp { public static void main(String[] args) { SpringApplication.run(Cache.class, args); } }
Memandangkan anda mahu boleh menggunakan cache, anda perlu mempunyai pengurus cache Bean Secara lalai, @EnableCaching
akan mendaftarkan ConcurrentMapCacheManager
Bean, dan tiada pengisytiharan kacang yang berasingan diperlukan. ConcurrentMapCacheManage
r menyimpan nilai dalam contoh ConcurrentHashMap
, yang merupakan pelaksanaan mekanisme cache yang paling mudah untuk thread-safe.
Pengurus cache lalai tidak dapat memenuhi keperluan kerana ia disimpan dalam memori jvm, jadi bagaimana untuk menyimpannya dalam redis? Pada masa ini anda perlu menambah pengurus cache tersuai.
1. Tambah kebergantungan
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2. Konfigurasikan pengurus cache Redis
@Configuration @EnableCaching public class CacheConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { return new LettuceConnectionFactory(); } @Bean public CacheManager cacheManager() { RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .disableCachingNullValues() .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory()) .cacheDefaults(redisCacheConfiguration) .build(); return redisCacheManager; } }
Sekarang kita mempunyai pengurus cache, bagaimana kita mengendalikan cache pada peringkat perniagaan?
Kita boleh menggunakan @Cacheable
, @CachePut
atau @CacheEvict
anotasi untuk mengendalikan cache.
Anotasi ini boleh cache hasil pelaksanaan kaedah Apabila kaedah dipanggil semula dalam had masa cache, kaedah itu sendiri tidak akan dipanggil, tetapi hasilnya akan diperolehi terus dari cache dan dikembalikan kepada pemanggil.
Contoh 1: Mencache hasil pertanyaan pangkalan data.
@Service public class MyService { @Autowired private MyRepository repository; @Cacheable(value = "myCache", key = "#id") public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } }
Dalam contoh ini, anotasi @Cacheable
digunakan untuk cache hasil kaedah getEntityById()
, yang mendapatkan semula objek MyEntity daripada pangkalan data berdasarkan ID
nya.
Tetapi bagaimana jika kami mengemas kini data? Data lama masih dalam cache?
Kemudian @CachePut
keluar Perbezaan daripada anotasi @Cacheable
ialah kaedah menggunakan anotasi @CachePut
tidak akan menyemak sama ada ia wujud dalam cache sebelum ini. Sebaliknya, kaedah akan dilaksanakan setiap kali dan hasil pelaksanaan akan ditulis ke cache yang ditentukan dalam bentuk pasangan nilai kunci. @CachePut
Anotasi biasanya digunakan untuk mengemas kini data cache, yang setara dengan cache menggunakan mod tulis dua kali dalam mod tulis.
@Service public class MyService { @Autowired private MyRepository repository; @CachePut(value = "myCache", key = "#entity.id") public void saveEntity(MyEntity entity) { repository.save(entity); } }
Kaedah yang ditandakan dengan anotasi @CacheEvict
akan mengalih keluar data yang disimpan daripada cache apabila ia dipanggil. @CacheEvict
Anotasi biasanya digunakan untuk memadam data cache, yang setara dengan cache menggunakan mod kegagalan dalam mod tulis.
@Service public class MyService { @Autowired private MyRepository repository; @CacheEvict(value = "myCache", key = "#id") public void deleteEntityById(Long id) { repository.deleteById(id); } }
@Caching
anotasi digunakan untuk menentukan berbilang anotasi berkaitan Spring Cache pada kaedah atau kelas pada masa yang sama.
Contoh 1: Atribut @Caching
dalam anotasi evict
menentukan dua cache untuk menjadi tidak sah apabila kaedah saveEntity
dipanggil.
@Service public class MyService { @Autowired private MyRepository repository; @Cacheable(value = "myCache", key = "#id") public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } @Caching(evict = { @CacheEvict(value = "myCache", key = "#entity.id"), @CacheEvict(value = "otherCache", key = "#entity.id") }) public void saveEntity(MyEntity entity) { repository.save(entity); } }
Contoh 2: Apabila memanggil kaedah getEntityById
, Spring akan terlebih dahulu menyemak sama ada hasilnya telah dicache dalam cache myCache
. Jika ya, Spring
akan mengembalikan hasil cache dan bukannya melaksanakan kaedah. Jika hasilnya belum dicache, Spring akan melaksanakan kaedah dan cache hasilnya dalam cache myCache
. Selepas kaedah dilaksanakan, Spring akan mengalih keluar hasil cache daripada cache @CacheEvict
berdasarkan anotasi otherCache
.
@Service public class MyService { @Caching( cacheable = { @Cacheable(value = "myCache", key = "#id") }, evict = { @CacheEvict(value = "otherCache", key = "#id") } ) public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } }
Contoh 3: Apabila memanggil kaedah saveData
, Spring akan terlebih dahulu mengalih keluar data daripada cache @CacheEvict
berdasarkan anotasi otherCache
. Spring kemudiannya akan melaksanakan kaedah dan menyimpan hasilnya ke pangkalan data atau API luaran. Selepas kaedah
dilaksanakan, Spring akan menambah hasil pada cache @CachePut
, myCache
dan myOtherCache
berdasarkan anotasi myThirdCache
. Spring juga akan menyemak sama ada keputusan telah dicache dalam @Cacheable
dan myFourthCache
cache berdasarkan anotasi myFifthCache
. Jika hasilnya belum dicache, Spring akan cache hasilnya dalam cache yang sesuai. Jika hasilnya telah dicache, Spring akan mengembalikan hasil cache dan bukannya melaksanakan kaedah itu semula.
@Service public class MyService { @Caching( put = { @CachePut(value = "myCache", key = "#result.id"), @CachePut(value = "myOtherCache", key = "#result.id"), @CachePut(value = "myThirdCache", key = "#result.name") }, evict = { @CacheEvict(value = "otherCache", key = "#id") }, cacheable = { @Cacheable(value = "myFourthCache", key = "#id"), @Cacheable(value = "myFifthCache", key = "#result.id") } ) public MyEntity saveData(Long id, String name) { // Code to save data to a database or external API MyEntity entity = new MyEntity(id, name); return entity; } }
通过@CacheConfig
注解,我们可以将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值:
@CacheConfig(cacheNames={"myCache"}) @Service public class MyService { @Autowired private MyRepository repository; @Cacheable(key = "#id") public MyEntity getEntityById(Long id) { return repository.findById(id).orElse(null); } @CachePut(key = "#entity.id") public void saveEntity(MyEntity entity) { repository.save(entity); } @CacheEvict(key = "#id") public void deleteEntityById(Long id) { repository.deleteById(id); } }
condition
作用:指定缓存的条件(满足什么条件才缓存),可用 SpEL
表达式(如 #id>0
,表示当入参 id 大于 0 时才缓存)
unless
作用 : 否定缓存,即满足 unless
指定的条件时,方法的结果不进行缓存,使用 unless
时可以在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)
//when id >10, the @CachePut works. @CachePut(key = "#entity.id", condition="#entity.id > 10") public void saveEntity(MyEntity entity) { repository.save(entity); } //when result != null, the @CachePut works. @CachePut(key = "#id", condition="#result == null") public void saveEntity1(MyEntity entity) { repository.save(entity); }
通过allEntries
、beforeInvocation
属性可以来清除全部缓存数据,不过allEntries
是方法调用后清理,beforeInvocation
是方法调用前清理。
//方法调用完成之后,清理所有缓存 @CacheEvict(value="myCache",allEntries=true) public void delectAll() { repository.deleteAll(); } //方法调用之前,清除所有缓存 @CacheEvict(value="myCache",beforeInvocation=true) public void delectAll() { repository.deleteAll(); }
Spring Cache注解中频繁用到SpEL表达式,那么具体如何使用呢?
SpEL 表达式的语法
Spring Cache可用的变量
通过Spring
缓存注解可以快速优雅地在我们项目中实现缓存的操作,但是在双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache
暂时没办法解决。最后我们再总结下Spring Cache使用的一些最佳实践。
只缓存经常读取的数据:缓存可以显着提高性能,但只缓存经常访问的数据很重要。很少或从不访问的缓存数据会占用宝贵的内存资源,从而导致性能问题。
根据应用程序的特定需求选择合适的缓存提供程序和策略。SpringBoot
支持多种缓存提供程序,包括 Ehcache
、Hazelcast
和 Redis
。
使用缓存时请注意潜在的线程安全问题。对缓存的并发访问可能会导致数据不一致或不正确,因此选择线程安全的缓存提供程序并在必要时使用适当的同步机制非常重要。
避免过度缓存。缓存对于提高性能很有用,但过多的缓存实际上会消耗宝贵的内存资源,从而损害性能。在缓存频繁使用的数据和允许垃圾收集不常用的数据之间取得平衡很重要。
使用适当的缓存逐出策略。使用缓存时,重要的是定义适当的缓存逐出策略以确保在必要时从缓存中删除旧的或陈旧的数据。
使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置。
常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了。
特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)。
Atas ialah kandungan terperinci Bagaimana untuk menggunakan cache dalam projek SpringBoot. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!