Spring Data JPA的使用及开启二级缓存

2024-01-17 09:29:48 浏览数 (2)

前言

Spring Data JPA 是基于 Hibernate 的。

Hibernate 是一个广泛使用的 Java ORM(对象关系映射)框架,它提供了对关系型数据库的映射和操作功能,使开发者能够以面向对象的方式来处理数据库操作,而不用直接编写 SQL 语句。

与 MyBatis 比较

  • Spring Data JPA:优点是代码简单、易于维护,集成 Spring 框架更方便; 缺点是灵活性不如 MyBatis,性能也可能不如 MyBatis。
  • MyBatis:优点是灵活性强,可以执行复杂的 SQL 语句; 缺点是需要手动编写 SQL 语句,难以维护。

环境

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.psvmc</groupId>
    <artifactId>z-api-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>z-api-jpa</name>
    <description>z-api-jpa</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

添加依赖

在项目的 pom.xml 文件中添加如下 Spring Data JPA 相关依赖:

代码语言:javascript复制
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
</dependency>

连接配置

application.properties配置文件中增加数据库参数,信息内容如下:

代码语言:javascript复制
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zbi_source?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=500000
spring.datasource.hikari.max-lifetime=540000
spring.datasource.hikari.connection-timeout=60000
spring.datasource.hikari.connection-test-query=SELECT 1

实体类

在项目中创建实体类,用于映射数据库表和列。

表实体

实体类需要使用@Entity注解进行标记,并且需要指定主键和自动生成策略。

例如:

代码语言:javascript复制
package cn.psvmc.zapijpa.entity;

import javax.persistence.*;

@Entity
@Table(name = "t_user")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "name", length = 20, nullable = false)
    private String name;

    @Column(name = "age")
    private Integer age;

    @Column(name = "sex")
    private Integer sex;

    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 Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }
}

主键生成

代码语言:javascript复制
@Id
@GeneratedValue(generator="system-uuid")
@GenericGenerator(name="system-uuid", strategy = "uuid")
@Column(name = "id")
private String id;

自动建表

默认JPA是不会自动建表的,但是如果想自动建表,可以添加配置。

代码语言:javascript复制
spring.jpa.hibernate.ddl-auto=update

设置 spring.jpa.hibernate.ddl-auto 属性为 createupdate

create 表示每次启动应用时都会删除现有表并重新创建。

update 表示每次启动应用时会根据实体类的定义,更新已存在的表结构(增加或修改列),但不会删除数据。如果表不存在也会创建。

一般来说使用 update,如果不想自动建表可以设置为none

关系映射

关系映射通常包括一对一、一对多和多对多等关系。

在 Spring Data JPA 中,可以使用 @OneToOne@OneToMany@ManyToMany 注解来标注关系映射。

这些注解通常与 @JoinColumn 注解一起使用,用于指定关联的外键列。

示例代码:

代码语言:javascript复制
@Entity
@Table(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Address> addresses;

    // 省略其他属性和方法
}

@Entity
@Table(name = "t_address")
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    // 省略其他属性和方法
}

在上例中,UserAddress 之间是一对多的关系,所以在 User 实体类中使用了 @OneToMany 注解,在 Address 实体类中使用了 @ManyToOne 注解。mappedBy 属性用于指定关联的属性名称,这里是 user,表示 Address 实体类中的 user 属性与 User 实体类中的 addresses 属性相对应。

cascade 属性表示级联操作,这里使用 CascadeType.ALL 表示在删除 User 实体时同时删除其关联的所有 Address 实体。

@JoinColumn 注解用于指定外键名称,这里是 user_id,表示 Address 表中的 user_id 列与 User 表中的主键相对应。

Repository

UserRepository.java

代码语言:javascript复制
package cn.psvmc.zapijpa.repository;

import cn.psvmc.zapijpa.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    UserEntity findByName(String name);
}

内置方法

在继承 Repository 接口后,会默认提供基本的增删改查方法,无需额外的代码实现即可使用。

常用的方法如下:

方法名

描述

T save(T entity)

保存实体对象

Iterable saveAll(Iterable entities)

批量保存实体对象

Optional findById(ID id)

根据主键获取实体对象

boolean existsById(ID id)

判断是否存在特定主键的实体对象

Iterable findAll()

获取所有实体对象

Iterable findAllById(Iterable ids)

根据主键批量获取实体对象

long count()

获取实体对象的数量

void deleteById(ID id)

根据主键删除实体对象

void delete(T entity)

删除实体对象

void deleteAll(Iterable<? extends T> entities)

批量删除实体对象

方法名称查询

方法名称查询是 Spring Data JPA 中最简单的一种自定义查询方法,并且不需要额外的注解或 XML 配置。

它通过方法名来推断出查询的条件,

例如以 findBy 开头的方法表示按照某些条件查询,以 deleteBy 开头的方法表示按照某些条件删除数据。

示例代码:

代码语言:javascript复制
public interface UserRepository extends Repository<UserEntity, Long> {
    // 根据用户名查询用户
    UserEntity findByName(String name);

    // 根据年龄查询用户列表
    List<UserEntity> findByAge(Integer age);

    // 根据用户名和密码查询用户
    UserEntity findByNameAndPassword(String name, String password);

    // 根据主键和用户名删除用户
    void deleteByIdAndUserName(Long id, String name);
}

规则

关键字

方法命名

sql where字句

And

findByNameAndPwd

where name= ? and pwd =?

Or

findByNameOrSex

where name= ? or sex=?

Is,Equals

findById,findByIdEquals

where id= ?

Between

findByIdBetween

where id between ? and ?

LessThan

findByIdLessThan

where id < ?

LessThanEquals

findByIdLessThanEquals

where id <= ?

GreaterThan

findByIdGreaterThan

where id > ?

GreaterThanEquals

findByIdGreaterThanEquals

where id > = ?

After

findByIdAfter

where id > ?

Before

findByIdBefore

where id < ?

IsNull

findByNameIsNull

where name is null

isNotNull,NotNull

findByNameNotNull

where name is not null

Like

findByNameLike

where name like ?

NotLike

findByNameNotLike

where name not like ?

StartingWith

findByNameStartingWith

where name like ‘?%’

EndingWith

findByNameEndingWith

where name like ‘%?’

Containing

findByNameContaining

where name like ‘%?%’

OrderBy

findByIdOrderByXDesc

where id=? order by x desc

Not

findByNameNot

where name <> ?

In

findByIdIn(Collection<?> c)

where id in (?)

NotIn

findByIdNotIn(Collection<?> c)

where id not in (?)

TRUE

findByStateTue

where state= true

FALSE

findByStateFalse

where state= false

IgnoreCase

findByNameIgnoreCase

where UPPER(name)=UPPER(?)

查询参数设置

除了方法名称查询外,还可以使用参数设置方式进行自定义查询。

它通过在方法上使用 @Query 注解来指定查询语句,然后使用 @Param 注解来指定方法参数与查询语句中的参数对应关系。

示例代码:

代码语言:javascript复制
public interface UserRepository extends Repository<UserEntity, Long> {
    // 根据用户名查询用户
    @Query("SELECT u FROM UserEntity u WHERE u.name = :name")
    UserEntity findByName(@Param("name") String name);

    // 根据用户名和密码查询用户
    @Query("SELECT u FROM UserEntity u WHERE u.name = :name AND u.password = :password")
    UserEntity findByUserNameAndPassword(@Param("name") String name, @Param("password") String password);
    
    @Query("SELECT u FROM UserEntity u WHERE u.name like  %:name%")
    List<UserEntity> findByName(@Param("name") String name);
}

使用 Native SQL 查询

在某些情况下,需要执行原生的 SQL 查询语句。

Spring Data JPA 提供了 @Query 注解来支持使用原生 SQL 查询数据。

@Query 注解中设置 nativeQuery=true 即可执行原生 SQL 语句。

以下示例代码演示了如何使用原生 SQL 查询 age 大于等于 18 的用户。

代码语言:javascript复制
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    @Query(value = "SELECT * FROM t_user WHERE age >= ?1", nativeQuery = true)
    List<UserEntity> findByAgeGreaterThanEqual(Integer age);
}

使用

代码语言:javascript复制
userRepository.findByAgeGreaterThanEqual(18);

这两种是一样的

代码语言:javascript复制
import cn.psvmc.zapijpa.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    @Query("SELECT u FROM UserEntity u WHERE u.name like  %:name%")
    List<UserEntity> findByName(@Param("name") String name);

    @Query(value = "SELECT * FROM t_user WHERE name like %?1%", nativeQuery = true)
    List<UserEntity> findByName2(@Param("name") String name);
}

让人难受的是下面的写法是对的,但是IDEA工具会提示错误。

排序和分页

在查询数据时,经常需要对结果进行排序和分页操作。

Spring Data JPA 提供了 SortPageable 两个类来实现排序和分页功能。

Sort 类表示排序规则,可以使用 Sort.by() 静态方法创建实例,并指定排序属性和排序方向。

常用方法如下:

方法名

描述

static Sort by(Sort.Order… orders)

根据排序规则创建 Sort 实例

static Sort.Order by(String property)

根据属性升序排序

static Sort.Order by(String property, Sort.Direction direction)

根据属性排序

示例代码:

代码语言:javascript复制
public interface UserRepository extends Repository<UserEntity, Long> {
    // 根据年龄升序查询用户列表
    List<UserEntity> findByOrderByAgeAsc();
}

Pageable 类表示分页信息,可以使用 PageRequest.of() 静态方法创建实例,并指定页码、每页数据量和排序规则。

常用方法如下:

方法名

描述

static PageRequest of(int page, int size, Sort sort)

创建分页信息实例

static PageRequest of(int page, int size, Sort.Direction direction, String… properties)

创建分页信息实例

示例代码:

代码语言:javascript复制
public interface UserRepository extends Repository<UserEntity, Long> {
    // 根据年龄降序分页查询用户列表
    Page<UserEntity> findBy(Pageable pageable);
}

使用

代码语言:javascript复制
Pageable pageable = PageRequest.of(0, 10, Sort.by("age").descending());
Page<UserEntity> page = userRepository.findBy(pageable);
List<UserEntity> userList = page.getContent();

更新和删除

在 Spring Data JPA 中,使用 updatedelete 语句需要使用 @Modifying 注解标注,并且需要添加 @Transactional 注解开启事务。

需要注意的是,@Modifying 注解只支持 DML 语句。

示例代码:

代码语言:javascript复制
public interface UserRepository extends Repository<UserEntity, Long> {
    // 更新用户密码
    @Modifying
    @Transactional
    @Query("UPDATE UserEntity u SET u.password = :password WHERE u.id = :id")
    void updatePasswordById(@Param("id") Long id, @Param("password") String password);

    // 删除年龄大于等于 age 的用户
    @Modifying
    @Transactional
    @Query("DELETE FROM UserEntity u WHERE u.age >= :age")
    void deleteByAgeGreaterThanEqual(@Param("age") Integer age);
}

Service

UserService.java

代码语言:javascript复制
package cn.psvmc.zapijpa.service;

import cn.psvmc.zapijpa.entity.UserEntity;
import cn.psvmc.zapijpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

@Service
@Transactional
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public List<UserEntity> userList() {
        return userRepository.findAll();
    }

    // 添加用户
    public UserEntity addUser(UserEntity user) {
        return userRepository.save(user);
    }

    // 更新用户
    public void updateUser(UserEntity user) {
        userRepository.save(user);
    }

    // 删除用户
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

Controller

代码语言:javascript复制
package cn.psvmc.zapijpa.controller;

import cn.psvmc.zapijpa.entity.UserEntity;
import cn.psvmc.zapijpa.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/list")
    public List<UserEntity> getUsers() {
        return userService.userList();
    }
}

访问

http://localhost:8080/user/list

多数据源

在实际应用中,有时需要使用多个数据源。

Spring Boot 提供了 @ConfigurationProperties@Primary@Qualifier 等注解来支持多数据源配置。

以下示例代码演示了如何在 Spring Boot 应用程序中配置多数据源。

application.properties 文件中配置两个数据源的连接信息

代码语言:javascript复制
# 数据源一
spring.datasource.one.url=jdbc:mysql://localhost:3306/test1
spring.datasource.one.username=root
spring.datasource.one.password=123456
# 数据源二
spring.datasource.two.url=jdbc:mysql://localhost:3306/test2
spring.datasource.two.username=root
spring.datasource.two.password=123456

创建两个数据源的配置类

DataSourceOneConfig

代码语言:javascript复制
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.one")
public class DataSourceOneConfig {
    private String url;
    private String username;
    private String password;

    // 省略 getter 和 setter 方法

    @Bean
    public DataSource dataSourceOne() {
        return DataSourceBuilder.create()
                .url(url)
                .username(username)
                .password(password)
                .build();
    }
}

DataSourceTwoConfig

代码语言:javascript复制
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.two")
public class DataSourceTwoConfig {
    private String url;
    private String username;
    private String password;

    // 省略 getter 和 setter 方法

    @Bean
    public DataSource dataSourceTwo() {
        return DataSourceBuilder.create()
                .url(url)
                .username(username)
                .password(password)
                .build();
    }
}

在 Service 或 Repository 中指定要使用的数据源

代码语言:javascript复制
@Service
public class UserService {
    @Autowired
    @Qualifier("dataSourceOne")
    private DataSource dataSourceOne;

    @Autowired
    @Qualifier("dataSourceTwo")
    private DataSource dataSourceTwo;

    public void addUser(User user) {
        try (Connection connection = dataSourceOne.getConnection();
             PreparedStatement statement = connection.prepareStatement(
                     "INSERT INTO user (name, age) VALUES (?, ?)"
             )
        ) {
            statement.setString(1, user.getName());
            statement.setInt(2, user.getAge());
            statement.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

在上述示例代码中,使用 @ConfigurationProperties 注解将数据源的连接信息和配置类绑定。

使用 @Qualifier@Autowired 注解指定要使用的数据源。

在 Service 或 Repository 中通过 DataSource.getConnection() 获取连接,手动执行 SQL 语句。

性能优化

开启批量操作

Mysql的话,开启批量操作需要在jdbc的url后面加上参数rewriteBatchedStatements=true,Oracle无需此操作。

默认批量操作是关闭的,要想开启设置如下参数

代码语言:javascript复制
spring.jpa.properties.hibernate.jdbc.batch_size=2
#开启批量插入
spring.jpa.properties.hibernate.order_inserts=true
#开启批量更新
spring.jpa.properties.hibernate.order_updates=true

batch_size设置值等于1的时候也是不生效的,必须大于1。

这里是为了测试才设置为2。实际使用可以大一点

代码语言:javascript复制
spring.jpa.properties.hibernate.jdbc.batch_size=100

为了方便验证我们可以添加打印配置

代码语言:javascript复制
spring.jpa.properties.hibernate.generate_statistics=true

这样当没有批量处理的时候会看到

spent executing 0 JDBC batches;

有批量的时候值是大于0的。

代码语言:javascript复制
import cn.psvmc.zapijpa.entity.UserEntity;
import cn.psvmc.zapijpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

@Service
@Transactional
public class UserService {
    @Autowired
    private UserRepository userRepository;
    // 批量添加用户
    @Transactional
    public List<UserEntity> addAll(List<UserEntity> users) {
        return userRepository.saveAll(users);
    }
}

注意

批处理必须开启事务。

Controller中

代码语言:javascript复制
import cn.psvmc.zapijpa.entity.UserEntity;
import cn.psvmc.zapijpa.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/list")
    public List<UserEntity> getUsers() {
        return userService.userList();
    }

    @RequestMapping("/add_users")
    public List<UserEntity> addUsers() {
        UserEntity u1 = new UserEntity("张三",10);
        UserEntity u2 = new UserEntity("李四",18);
        UserEntity u3 = new UserEntity("王五",22);
        UserEntity u4 = new UserEntity("赵六",16);
        List<UserEntity> userList = Arrays.asList(u1, u2, u3, u4);
        return  userService.addAll(userList);
    }
}

当我们调用以下地址的时候会插入4条数据

http://localhost:8080/user/add_users

开启批量设置为2的时候,没两条就进行一次批处理,就会看到显示2次批处理了。

spent executing 2 JDBC batches;

有人说是通过打印SQL查看

代码语言:javascript复制
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=false
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

但是实测发现就算是批处理,SQL也是一次一条,只不过是多条后批量提交。

使用二级缓存

在使用 Spring Data JPA 进行数据访问时,可以使用二级缓存来提高程序的性能。

注意

这里使用的不是基于Hibernate 的Ehcache实现。

使用注意

二级缓存也存在一些潜在的问题,如缓存的数据可能不是最新的(缓存不一致)、缓存数据的内存占用等。 因此,在使用二级缓存时,需要根据具体的业务场景和需求来决定是否使用以及如何配置和管理缓存。

以下演示了如何在 Spring Boot 应用程序中配置 Ehcache 作为二级缓存。

添加依赖

pom.xml 文件中添加 Ehcache 依赖

代码语言:javascript复制
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

添加Ehcache 配置

创建 Ehcache 配置文件 ehcache.xml

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd"
         updateCheck="false">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
            maxElementsInMemory="10000"
            maxElementsOnDisk="0"
            eternal="false"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="3"
            timeToLiveSeconds="3"
            diskSpoolBufferSizeMB="50"
            diskExpiryThreadIntervalSeconds="10"
            memoryStoreEvictionPolicy="LFU"/>
    <cache name="userService.getUserByName"
           maxEntriesLocalHeap="10000"
           eternal="false"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="3"
           timeToLiveSeconds="3">
    </cache>
</ehcache>

在上述示例代码中,创建了名为 userService.getUserByName 的缓存区域,设置了最大缓存数、缓存时间等属性。

参数解释

各个熟悉的含义

  • name 缓存空间名称(非缓存key)
  • maxElementsInMemory:设置了缓存的上限,最多存储多少个记录对象
  • maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大
  • eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
  • overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。
  • diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。
  • diskPersistent:是否缓存虚拟机重启期数据,设置成true表示缓存虚拟机重启期数据
  • diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒
  • timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地处于空闲状态
  • timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。 当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。 如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义
  • memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)

添加配置

application.properties 文件中启用二级缓存

代码语言:javascript复制
spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache.xml

添加配置类

代码语言:javascript复制
package cn.psvmc.zapijpa.cache;

import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Objects;

@Configuration
@EnableConfigurationProperties(CacheProperties.class) // 这样写的好处就是与配置文件的配置信息进行绑定
public class EhCacheConfiguration {

    private final CacheProperties cacheProperties;

    public EhCacheConfiguration(CacheProperties cacheProperties) {
        this.cacheProperties = cacheProperties;
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactory() {
        EhCacheManagerFactoryBean ehCacheManagerFactory = new EhCacheManagerFactoryBean();
        ehCacheManagerFactory.setConfigLocation(cacheProperties.resolveConfigLocation(cacheProperties.getEhcache().getConfig()));
        ehCacheManagerFactory.setShared(true);
        return ehCacheManagerFactory;
    }

    @Bean
    public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean ehCacheManagerFactory) {
        return new EhCacheCacheManager(Objects.requireNonNull(ehCacheManagerFactory.getObject()));
    }
}

代码配置

设置缓存

代码语言:javascript复制
import cn.psvmc.zapijpa.entity.UserEntity;
import cn.psvmc.zapijpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.List;

@Service
@Transactional
@EnableCaching
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    // 根据名称获取用户列表
    @Cacheable(cacheNames = "userService.getUserByName",key = "#name")
    public List<UserEntity> getUserByName(String name) {
        return userRepository.findByName(name);
    }
    
    // 批量添加用户
    @Transactional
    @CacheEvict(cacheNames = "userService.getUserByName",allEntries = true)
    public List<UserEntity> addAll(List<UserEntity> users) {
        return userRepository.saveAll(users);
    }
}

需要缓存的地方在类上添加@EnableCaching

在方法上添加@Cacheable(cacheNames = "userService.getUserByName",key = "#name")

其中

  • cacheNames 时缓存的名称,和XML配置中的对应。
  • key 如果方法有参数,可以放在key上。这样参数不同都可以产生新的缓存。

删除缓存@CacheEvict(cacheNames = "userService.getUserByName",allEntries = true)

当执行addAll的时候,因为用户改变了,所以我们清除缓存。

allEntries:是否清除这个缓存(cacheNames)中的所有数据。默认false。

无论我们缓存的时候是否设置了key,都要设置allEntries = true,否则无法删除缓存。

相关注解或概念

说明

@EnableCaching

开启基于注解的缓存

@Cacheable

主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,缓存后再次调用方法则直接返回缓存内容。

@CachePut

保证方法被调用,并缓存结果。常用于更新数据。

@CacheEvict

清空缓存

注意注解引用的类

代码语言:javascript复制
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;

Controller

代码语言:javascript复制
import cn.psvmc.zapijpa.entity.UserEntity;
import cn.psvmc.zapijpa.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/get_user")
    public List<UserEntity> getUser() {
        return  userService.getUserByName("张");
    }

    @RequestMapping("/get_user2")
    public List<UserEntity> getUser2() {
        return  userService.getUserByName("王");
    }
    
    @RequestMapping("/add_users")
    public List<UserEntity> addUsers() {
        UserEntity u1 = new UserEntity("张三",10);
        UserEntity u2 = new UserEntity("李四",18);
        UserEntity u3 = new UserEntity("王五",22);
        UserEntity u4 = new UserEntity("赵六",16);
        List<UserEntity> userList = Arrays.asList(u1, u2, u3, u4);
        return  userService.addAll(userList);
    }
}

访问

http://localhost:8080/user/get_user

http://localhost:8080/user/get_user2

http://localhost:8080/user/add_users

这样上面的两个请求都会在第一次的时候查询数据库,后面的请求都用的缓存。

超时时间未生效

关键的问题在于没有指定缓存类型为ehcacheehcache.xml文件压根就没有生效。

springboot使用默认的SimpleCacheConfiguration,不是用的ehcache

解决方法如下:

application.properties添加如下

代码语言:javascript复制
spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache.xml

打印执行的SQL

代码语言:javascript复制
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=false
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

0 人点赞