SpringBoot缓存详解

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的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值