SpringBoot缓存详解(一)Ehcache
首先说一下缓存是做什么的,能解决什么问题。做后端程序开发的人员都知道一个程序的瓶颈在于数据库,大家也知道内存的速度是大大快于硬盘的速度的。
在开发过程中,当我们需要重复的获取数据库里面的相同数据的时候,我们一次又一次的请求数据库或者远程数据服务,导致大量的时间耗费在数据库查询或者远程方法的调用上,致使我们软件的性能低下,大量占用系统的CPU。缓存就是解决这些问题的。
知道了缓存是干嘛的,下面开始讲解Spring Boot中缓存的使用。
1、缓存的分类
在Spring boot中,根据业务场景,缓存大致分为两类,一类是单机版缓存,一类是集中制缓存,单机版以Ehcache为例,集中制缓存以Redis为例,后面第二篇会介绍。
单机版缓存意思就是只缓存在本机服务器上的缓存,适用于中小型项目。
准备工作
这里用到的技术是spring boot +jpa +Ehcache
新建一个SpringBoot项目,这里不做介绍,修改pom文件,添加一下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 添加ehcache 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SpringBoot集成mybatis框架 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.starter.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.spring.boot.starter.version}</version>
</dependency>
<!--阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--io常用工具类 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
对应的版本
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mybatis.spring.boot.starter.version>1.3.2</mybatis.spring.boot.starter.version>
<pagehelper.spring.boot.starter.version>1.2.5</pagehelper.spring.boot.starter.version>
<druid.version>1.1.14</druid.version>
<commons.io.version>2.5</commons.io.version>
</properties>
修改application.yml文件,填写自己的数据库名称、用户名和密码
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
url: jdbc:mysql://localhost:3306/cache_test?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username:
password:
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
login-username: admin
login-password: admin
filter: stat,wall,slf4j
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
jpa:
show-sql: true
hibernate:
ddl-auto: update
cache:
ehcache:
config: ehcache.xml
server:
port: 9999
在resources目录下,新建ehcache.xml,对ehcache进行配置,SpringBoot会自定检测ehcache.xml文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<cache name="users"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
ehcache 有很多属性,可以自己百度搜索查询,平时用的最多的是这四个属性:maxElementsInMemory=“10000” 缓存中元素个数
eternal=“false” 缓存是否永久有效
timeToIdleSeconds=“120” 当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除;即缓存自创建日期起能够存活的最长时间,单位为秒(s)
timeToLiveSeconds=“120” 当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空;即缓存被创建后,最后一次访问时间到缓存失效之时,两者之间的间隔,单位为秒(s)
编写对应的实体类,dao层和server层
/**
* User 实体.
*/
@Entity // 实体
public class User implements Serializable {
@Id // 主键
@GeneratedValue(strategy= GenerationType.IDENTITY) // 自增策略
private Long id; // 实体一个唯一标识
@Column(nullable = false, length = 20) // 映射为字段,值不能为空
private String name;
@Column(nullable = false, length = 50, unique = true)
private String email;
@Column(nullable = false, length = 20, unique = true)
private String username; // 用户账号,用户登录时的唯一标识
@Column(length = 100)
private String password; // 登录时密码
@Column(length = 200)
private String perms; // 授权码
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
protected User() { // 无参构造函数;设为 protected 防止直接使用
}
public User(String name, String email, String username, String password, String perms) {
this.name = name;
this.email = email;
this.username = username;
this.password = password;
this.perms = perms;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", perms='" + perms + '\'' +
'}';
}
}
dao层
public interface UserRepository extends JpaRepository<User, Long> {
/**
* 根据用户姓名分页查询用户列表
* @param name
* @param pageable
* @return
*/
Page<User> findByNameLike(String name, Pageable pageable);
/**
* 根据用户账号查询用户
* @param username
* @return
*/
User findByUsername(String username);
}
service层
ublic interface UserService {
/**
* 新增、编辑、保存用户
* @param user
* @return
*/
User saveOrUpateUser(User user);
/**
* 删除用户
* @param id
*/
void removeUser(Long id);
/**
* 根据id获取用户
* @param id
* @return
*/
Optional<User> getUserById(Long id);
/**
* 根据用户名进行分页模糊查询
* @param pageable
* @return
*/
Page<User> findAll(Pageable pageable);
/**
* 根据用户名进行分页模糊查询
* @param name
* @param pageable
* @return
*/
Page<User> listUsersByNameLike(String name, Pageable pageable);
/**
* 根据用户名查询
* @param username
* @return
*/
User getUserByUserName(String username);
List<User> getUserAll();
}
service的实现类
@Transactional
@Service
@CacheConfig(cacheNames = "users")
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
//@CachePut缓存新增的或更新的数据到缓存,其中缓存名字是 uesr 。数据的key是person的id
@CachePut
public User saveOrUpateUser(User user) {
return userRepository.save(user);
}
@Override
//@CacheEvict 从缓存people中删除key为id 的数据 ,只会删除自己id的缓存
@CacheEvict
public void removeUser(Long id) {
userRepository.deleteById(id);
}
@Override
//@Cacheable缓存key为user 的id 数据到缓存users中,如果没有指定key则方法参数作为key保存到缓存中
@Cacheable
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
@Override
public Page<User> findAll(Pageable pageable) {
return userRepository.findAll(pageable);
}
@Override
public Page<User> listUsersByNameLike(String name, Pageable pageable) {
// 模糊查询
name = "%" + name + "%";
Page<User> users = userRepository.findByNameLike(name, pageable);
return users;
}
@Override
public User getUserByUserName(String username) {
return userRepository.findByUsername(username);
}
@Override
public List<User> getUserAll() {
List<User> users =userRepository.findAll();
return users;
}
}
在启动类上添加缓存注解
@EnableCaching
测试用例如下:
@Autowired
private UserRepository userRepository;
@Autowired
private UserService userService;
@Autowired
private CacheManager cacheManager;
@Test
public void test() throws Exception {
Optional<User> u1 = userService.getUserById(1L);
System.out.println("第一次查询:" + u1.get().getName());
// User u2 = userRepository.findByUsername("username");
Optional<User> u2 = userService.getUserById(1L);
System.out.println("第二次查询:" +u2.get().getName());
u1.get().setName("DDDD");
userService.saveOrUpateUser(u1.get());
// User u3 = userRepository.findByUsername("username");
Optional<User> u3 = userService.getUserById(1L);
System.out.println("第三次查询:" + u3.get().getName());
Optional<User> u4 = userService.getUserById(1L);
System.out.println("第四次查询:" + u4.get().getName());
Optional<User> u6 = userService.getUserById(2L);
System.out.println("第一次查询:" + u6.get().getName());
测试结果:
Hibernate: select user0_.id as id1_0_0_, user0_.email as email2_0_0_, user0_.name as name3_0_0_, user0_.password as password4_0_0_, user0_.perms as perms5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=?
第一次查询:DDDD
第二次查询:DDDD
Hibernate: select user0_.id as id1_0_0_, user0_.email as email2_0_0_, user0_.name as name3_0_0_, user0_.password as password4_0_0_, user0_.perms as perms5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=?
第三次查询:DDDD
第四次查询:DDDD
Hibernate: select user0_.id as id1_0_0_, user0_.email as email2_0_0_, user0_.name as name3_0_0_, user0_.password as password4_0_0_, user0_.perms as perms5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=?
第一次查询:AAA
可以看出,在第二次查询之后,就不在执行SQl查询语句,说明数据的查询时来自于缓存,这样就可以避免多次查询数据库。
总结
Cache注解的主要作用,以及常用的注解:
@CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = “users”):配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable自己配置缓存集的名字来定义。
@Cacheable:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。
@CachePut:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable类似,具体功能可参考上面对@Cacheable参数的解析
@CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。
本章就讲解到这里,下一讲,将介绍SpringBoot+Redis的使用。