前言
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
属性为 create
或 update
。
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;
// 省略其他属性和方法
}
在上例中,User
和 Address
之间是一对多的关系,所以在 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
的用户。
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 提供了 Sort
和 Pageable
两个类来实现排序和分页功能。
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 中,使用 update
和 delete
语句需要使用 @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
文件中配置两个数据源的连接信息
# 数据源一
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 依赖
<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
<?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
文件中启用二级缓存
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
这样上面的两个请求都会在第一次的时候查询数据库,后面的请求都用的缓存。
超时时间未生效
关键的问题在于没有指定缓存类型为ehcache
,ehcache.xml
文件压根就没有生效。
springboot使用默认的SimpleCacheConfiguration
,不是用的ehcache
。
解决方法如下:
application.properties
添加如下
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