preface

The vast majority of systems are read more write less, as we all know, memory access speed is very fast, is ten times the speed of disk access, if you do not use cache, through the database access to the hard disk, for double 11 such a large transaction volume is unimaginable. Someone wrote a paper called “Let the CPU Tell you How Slow your Hard disk and Network really are,” which describes the data processing speed of disk, memory, and network in terms of human perception.

    1. Reading 1MB of continuous data from memory takes about 250us, or 7.5 days in human time.
    1. Reading 1MB of sequential data from an SSD takes about 1ms, which translates to a month in human time.

Thus, the gap between memory and hard disk is equivalent to that between a tractor and a Ferrari.

Why Caffeine

In Caffeine (1), a Java native cache wizard, the performance of Caffeine has been compared to other caching frameworks, and the results show that Caffeine’s performance is much better than other caching frameworks. For more information on caffeine, check out the Java native Cache Wizard, Caffeine (1) and Java Native Cache Wizard, Caffeine (2).

Caffeine SpringBoot integration

SpringBoot default SimpleCacheConfiguration, which USES ConcurrentMapCacheManager to implement caching, The ConcurrentMapCache is essentially a Built-in Java ConcurrentHashMap collection object that needs to reference Caffeine’s dependency if Caffeine needs to be used.

1. Configure Maven dependency for Caffeine

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
</dependency>
Copy the code

2. Enable caching in SpringBoot

@enablecaching public class DemoApplication {public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}Copy the code

3. Assemble Caffeine through beans

@Configuration public class CaffeineConfig { @Autowired BookDao bookDao; @Autowired CacheLoader cacheLoader; @Bean @Primary public CacheManager caffeineCache() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); InitialCapacity (10) //maximumSize Is used to control the maximum number of buffers in the cache. MaximumSize and maximumWeight cannot be used together.  .maximumSize(100) .expireAfterAccess(5, TimeUnit.SECONDS) .removalListener((Integer key, Object value, RemovalCause cause) - > System. Out. Printf (" time: % d, Key: % d, value: % s, remove the cause (% s) % n ", System. CurrentTimeMillis () / 1000, RefreshAfterWrite cacheLoader. RefreshAfterWrite (5, timeUnit.seconds); cacheManager.setCaffeine(caffeine); / / cache loading strategy, when the key does not exist or key expired can be to obtain data through CacheLoader cacheManager. SetCacheLoader (CacheLoader); return cacheManager; } @Bean public CacheLoader<Object, Object> cacheLoader() { CacheLoader<Object, Object> cacheLoader = new CacheLoader<Object, Object>() {@override public Object Load (Object key) throws Exception {system.out.println (" reload data from the database :" + key); return bookDao.getBookById((int)key); } // Override public Object reload(Object key, Object oldValue) throws Exception {// the reload policy can be handled here. In this example, no reload is handled, just the oldValue is returned. return oldValue; }}; return cacheLoader; }}Copy the code

4. Define entity objects

@Getter
@Setter
@ToString
public class BookBean implements Serializable {
    private static final long serialVersionUID = -6585766340444705937L;
    private int bookId;
    private String bookName;
    private String author;
    private float price;
}
Copy the code

5. Define access database classes

@Component public class BookDao {/** ** private static Map<Integer, BookBean> bookMap = new HashMap<>(); @PostConstruct private void initDB() { BookBean xiyou = new BookBean(); xiyou.setBookId(1); Xiyou.setbookname (" journey to the West "); SetAuthor (" Wu Cheng 'en "); Xiyou. SetPrice (55.5 f); bookMap.put(1, xiyou); BookBean honglou = new BookBean(); honglou.setBookId(2); Honglou. SetBookName (" Dream of Red Mansions "); Honglou. SetAuthor (" Cao Xueqin "); Honglou. SetPrice (66.6 f); bookMap.put(2, honglou); } public BookBean getBookById(int bookId) { return bookMap.get(bookId); } public BookBean update(BookBean bookBean) { bookMap.put(bookBean.getBookId(), bookBean); return bookMap.get(bookBean.getBookId()); } public BookBean save(BookBean bookBean) { bookMap.put(bookBean.getBookId(), bookBean); return bookMap.get(bookBean.getBookId()); } public void delete(int bookId) { bookMap.remove(bookId); }}Copy the code

6. Define service interfaces

public interface BookService {

    public BookBean getBookById(int bookId);

    public BookBean updata(BookBean bookBean);

    public BookBean save(BookBean bookBean);

    public String delete(int bookId);

}
Copy the code

7. Define service implementation classes

@service // You can specify the cacheNames name @Cacheconfig (cacheNames = {"book"}) public class BookServiceImpl implements BookService { @Autowired BookDao bookDao; /** * If cache exists, read cache value directly; If the cache does not exist, the target method is called and the result is placed in the cache * value, cacheNames: two identical arguments (cacheNames is new to Spring 4 as an alias for Value) that specify the collection name * key to be cached: Cacheable(key = "#p0"); Cacheable(key = "#p0"); @param bookId @return @cacheable (cacheNames = {"book"}, key = "#bookId") // @Cacheable(value = "book" ,key = "targetClass + methodName +#p0") @Override public BookBean GetBookById (int bookId) {system.out.println (" query database "); getBookById(int bookId) {system.out.println (" query database "); return bookDao.getBookById(bookId); } @CachePut(cacheNames = {"book"}, Key = "# bookbean.bookid ") @override public bookBean updata(bookBean bookBean) {system.out.println (" update database :" + bookBean.toString()); return bookDao.update(bookBean); } @cacheput (cacheNames = {"book"}, key = "# bookbean.bookid ") @override public BookBean Save (BookBean BookBean) {system.out.println (" Save to database :" + bookBean.toString()); return bookDao.save(bookBean); } /** * CacheEvict is used to remove data from the cache * allEntries=true: Clear all cache with cacheName book after method invocation * beforeInvocation=true: Clear all cache before method invocation ** @param bookId * @return */ @CacheEvict(cacheNames = {"book"}) @Override public String delete(int bookId) { bookDao.delete(bookId); Return C. }}Copy the code

8. Define the controller

@RestController @RequestMapping("book") public class BookController { @Autowired BookService bookService; @GetMapping("get/{bookId}") @ResponseBody public BookBean getBook(@PathVariable("bookId") int bookId) { System.out.println(" time :"+ system.currentTimemillis ()/1000+", query book interface is called "); return bookService.getBookById(bookId); } @PostMapping("updata") @ResponseBody public BookBean updata(@RequestBody BookBean bookBean) { System.out.println(" time :"+ system.currentTimemillis ()/1000+", update book interface is called "); return bookService.updata(bookBean); } @PostMapping("save") @ResponseBody public BookBean save(@RequestBody BookBean bookBean) { System.out.println(" time :"+ system.currentTimemillis ()/1000+", save book interface is called "); return bookService.save(bookBean); } @PostMapping("delete/{bookId}") @ResponseBody public String delete(@PathVariable("bookId") int bookId) { System.out.println(" time :"+ system.currentTimemillis ()/1000+", delete book interface is called "); return bookService.delete(bookId); }}Copy the code

9. The test

1. Query tests

(1) Two consecutive visitshttp://localhost:8080/book/get/2



You can see that on the second access, the data is queried directly from the cache and not reloaded from the database

(2) Wait more than five seconds to visit againhttp://localhost:8080/book/get/2



We can see that data will be reloaded from the database and that caffeine will recycle access time

(3) Test the impact of the modification on the cache

  • Visit http://localhost:8080/book/get/2 for the second time in a row first
  • Visit http://localhost:8080/book/updata within five seconds, and then call the HTTP post method, modified the price of a dream of red mansions is 99.9 entity data is: {” bookId “: 2,” bookName “:” a dream of red mansions “, “author” : “Price “:99.9}
  • And then visit http://localhost:8080/book/get/2



As we can see, the first call to the interface query reloads data from the database because the reclaim policy is triggered. The second time the interface query is invoked, the data is retrieved from the cache. Third call interface modification, will update the database, and update the cache; The fourth time to query the interface, the data will be directly obtained from the cache.

The source code

The source code in github.com/coding-hao/… In the spring_cache project below

Welcome to follow my wechat official account: CodingTao