Spring Boot 整合NoSQL
NoSQL 是指非关系型数据库,非关系型数据库和关系型数据库两者存在许多显著的不同点,其中最重要的是NoSQL 不使用SQL 作为查询语言。其数据存储可以不需要固定的表格模式,一般都有水平可扩展性的特征。NoSQL 主要有如下几种不同的分类:
- Key/Value 键值存储。这种数据存储通常都是无数据结构的,一般被当作字符串或者二进制数据,但是数据加载速度快,典型的使用场景是处理高并发或者用于日志系统等,这一类的数据库有Redis 、Tokyo Cabinet 等。
- 列式存储数据库。列存储数据库功能相对局限,但是查找速度快,容易进行分布式扩展,一般用于分布式文件系统中,这一类的数据库有HBase 、Cassandra 等。
- 文档型数据库。和Key/Value 键值存储类似,文档型数据库也没有严格的数据格式,这既是缺点也是优势,因为不需要预先创建表结构,数据格式更加灵活, 一般可用在Web 应用中,这一类数据库有MongoDB 、CouchDB 等。
- 图形数据库。图形数据库专注于构建关系图谱,例如社交网络,推荐系统等,这一类的数据库有Neo4J 、DEX 等。
NoSQL种类繁多,Spring Boot对大多数NoSQL都提供了配置支持。
整合Redis
Redis 简介
Redis是一个使用C编写的基于内存的NoSQL 数据库,它是目前最流行的键值对存储数据库。Redis 由一个Key、Value映射的字典构成, 与其他NoSQL 不同, Redis 中Value 的类型不局限于字符串,还支持列表、集合、有序集合、散列等。Redis 不仅可以当作缓存使用,也可以配置数据持久化后当作NoSQL 数据库使用,目前支持两种持久化方式:快照持久化和AOF 持久化。另一方面,Redis也可以搭建集群或者主从复制结构,在高并发环境下具有高可用性。
Redis 安装
Redis安装可以参考:https://www.runoob.com/redis/redis-install.html
整合Spring Boot
Redis的Java客户端有很多,例如Jedis、JRedis、Spring Data Redis等,Spring Boot借助于Spring Data Redis为Redis提供了开箱即用自动化配置,开发者只需要添加相关依赖并配置Redis连接信息即可,具体整合步骤如下。
- 创建Spring Boot项目
首先创建Spring Boot Web 项目,添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 配置Redis
接下来在application.properties 中配置Redis 连接信息,代码如下:
# Redis属性配置
spring.redis.database=0
spring.redis.host=192.168.86.3
spring.redis.port=6379
spring.redis.password=password
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
# 缓存配置
spring.cache.cache-names=c1,c2
spring.cache.redis.time-to-live=1800s
- 第1~4行是基本连接信息配置,第5~8 行是连接池信息配置。
- 第1行配置表示使用的Redis库的编号,Redis中提供了16个database,编号为0~15 。
- 第2行配置表示Redis 实例的地址。
- 第3行配置表示Redis 立起口号, 默认是6379 。
- 第4行配置表示Redis 登录密码。
- 第5行配置表示Redis 连接池的最大连接数。
- 第6行配置表示Redis 连接池中的最大空闲连接数。
- 第7行配置表示连接池的最大阻塞等待时间,默认为-1 ,表示没有限制。第8行配置表示连接池最小空闲连接数。
- 如果项目使用了Lettuce,则只需将第5-8行配置中的jedis修改为lettuce即可。
Spring Boot缓存
Spring 3.1 中开始对缓存提供支持,核心思路是对方法的缓存,当开发者调用一个方法时,将方法的参数和返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从缓存中获取,否则再去执行该方法。但是, Spring 中并未提供缓存的实现,而是提供了一套缓存API,开发者可以自由选择缓存的实现,目前Spring Boot 支持的缓存有如下几种:
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- lnfinispan
- Couchbase
- Redis
- Caffeine
- Simple
本章将介绍目前常用的缓存实现Redis 。由于Spring 早己将缓存领域统一,因此无论使用哪种缓存实现,不同的只是缓存配置,开发者使用的缓存注解是一致的( Spring 缓存注解和各种缓存实现的关系就像JDBC 和各种数据库驱动的关系一样)。
对于缓存 - 内存的速度远远大于硬盘的速度
- 缓存主要是在获取资源方便性能优化的关键方面
- Redis 是缓存数据库
- 缓存未命中解决与防止缓存击穿
- 开启缓存
在项目的入口类上添加@EnableCaching 注解开启缓存,代码如下:
package org.gary.rediscache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class RediscacheApplication {
public static void main(String[] args) {
SpringApplication.run(RediscacheApplication.class, args);
}
}
- 创建BookDao
创建Book 实体类和BookService ,代码如下:
package org.gary.rediscache;
import java.io.Serializable;
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
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 String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
package org.gary.rediscache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
@Cacheable
public Book getBookById(Integer id) {
System.out.println("getBookById");
Book book = new Book();
book.setId(id);
book.setName("三国演义");
book.setAuthor("罗贯中");
return book;
}
@CachePut(key = "#book.id")
public Book updateBookById(Book book) {
System.out.println("updateBookById");
book.setName("三国演义2");
return book;
}
@CacheEvict(key = "#id")
public void deleteBookById(Integer id) {
System.out.println("deleteBookById");
}
}
代码解释:
• 在BookDao上添加@CacheConfig 注解指明使用的缓存的名字,这个配置可选,若不使用@CacheConfig 注解,则直接在@Cacheable 注解中指明缓存名字。
• 第4 行在getBookByld 方法上添加@Cacheable 注解表示对该方法进行缓存,默认情况下,缓存的key是方法的参数,缓存的value是方法的返回值。当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有,则直才妾使用缓存数据,该方法不会执行,否则执行该方法,执行成功后将返回值缓存起来,但若是在当前类中调用该方法,则缓存不会生效。
• @Cacheable 注解中还有一个属性condition 用来描述缓存的执行时机,例如@Cacheable(condition =”#id%2==0”)表示当id 对2 取模为0 时才进行缓存,否则不缓存.
• 如果开发者不想使用默认的key,也可以像第13行和第19行一样自定义key,第13 行表示缓存的key 为参数book 对象中id 的值,第19 行表示缓存的key 为参数id . 除了这种使用参数定义key 的方式之外, Spring 还提供了一个root 对象用来生成key
• 如果这些key 不能够满足开发需求,开发者也可以自定义缓存key的生成器KeyGenerator,代码如下:
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Arrays;
@Component
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return Arrays.toString(params);
}
}
@Service
@CacheConfig(cacheNames = "book_cache")
public class BookDao {
@Autowired
MyKeyGenerator myKeyGenerator;
@Cacheable(keyGenerator = "myKeyGenerator")
public Book getBookById(Integer id) {
System.out.println("getBookById");
Book book = new Book();
book.setId(id);
book.setName("三国演义");
book.setAuthor("罗贯中");
return book;
}
@CachePut(key = "#book.id")
public Book updateBookById(Book book) {
System.out.println("updateBookById");
book.setName("三国演义2");
return book;
}
@CacheEvict(key = "#id")
public void deleteBookById(Integer id) {
System.out.println("deleteBookById");
}
}
自定义MyKeyGenerator 实现KeyGenerator 接口,然后实现该接口中的generate 方法,该方法的三个参数分别是当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息组成一个新的key 返回,返回值就是缓存的key。在@Cacheable 注解中引用MyKeyGenerator 实例即可。
• @CachePut主解一般用于数据更新方法上,与@Cacheable 注解不同,添加了@CachePut 注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来,如果该key 对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。同时,@CachePut 具有和@Cacheable 类似的属性,这里不再赘述。
• @CacheEvict 注解一般用于删除方法上,表示移除一个key 对应的缓存。@CacheEvict注解有两个特殊的属性:allEntries 和beforelnvocation,其中allEntries 表示是否将所有的缓存款据都移除, 默认为false,beforelnvocation表示是否在方法执行之前移除缓存中的数据,默认为false,即在方法执行之后移除缓存中的数据。
- 创建测试类
创建测试类, 对Service 中的方法进行测试,代码如下:
package org.gary.rediscache;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
class RediscacheApplicationTests {
@Autowired
BookDao bookDao;
@Test
public void contextLoads() {
bookDao.getBookById(1);
bookDao.getBookById(1);
bookDao.deleteBookById(1);
Book b3 = bookDao.getBookById(1);
System.out.println("b3:" + b3);
Book b = new Book();
b.setName("三国演义");
b.setAuthor("罗贯中");
b.setId(1);
bookDao.updateBookById(b);
Book b4 = bookDao.getBookById(1);
System.out.println("b4:" + b4);
}
}
执行该方法,控制台打印日志如图所示:
一开始执行了两个查询,但是查询方法只打印了一次,因为第二次使用了缓存。接下来执行了删除方法,删除方法执行完之后再次执行查询, 查询方法又被执行了,因为在删除方法中缓存己经被删除了。再接下来执行更新方法,更新方法中不仅更新数据,也更新了缓存,所以在最后的查询方法中, 查询方法日志没打印,说明该方法没执行,而是使用了缓存中的数据,而缓存中的数据已经被更新了。
Spring Boot整合Redis与Cache实现缓存Demo:添加链接描述