相关依赖
SpringBoot整合SpringData Jpa所需的基本依赖:
<dependencies>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置文件
springBoot整合JPA的基本配置:
# Spring Boot配置
spring:
# 数据库配置
datasource:
# 数据源驱动类名
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据库连接URL
url: jdbc:mysql://localhost:3306/my_admin?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
# 数据库用户名
username: root
# 数据库密码
password: 123456
jackson: #统一日期格式
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# JPA配置
jpa:
# JPA实现提供商
hibernate:
# 自动创建/更新数据库表结构
ddl-auto: update
# 数据库方言
database-platform: org.hibernate.dialect.MySQL8Dialect
#控制台打印sql语句
show-sql: true
# 每个HTTP请求都会打开一个Session,并在整个请求结束后关闭Session。这样可以确保在视图层渲染时仍然可以访问到延迟加载的关联实体对象
open-in-view: true
# 日志配置
logging:
# 日志级别
level:
root: info
org.springframework: info
com.example.projectname: debug
JPA实现基本的增删改查
实体类
创建与数据库表对应的实体类,并使用JPA注解进行映射
@Entity
@Table(name = "user", schema = "my_admin")
@Where(clause = "is_delete=0")
@DynamicInsert
@DynamicUpdate
@EntityListeners(AuditingEntityListener.class)
@Getter
@Setter
public class UserEntity {
@Id
@Column(name = "id")
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid",strategy = "uuid")
private String id;
@Basic
@Column(name = "real_name")
private String realName;
@Basic
@Column(name = "username")
private String username;
@Basic
@Column(name = "password")
private String password;
@Basic
@Column(name = "address")
private String address;
@Basic
@Column(name = "phone")
private String phone;
@Basic
@Column(name = "gender")
private Integer gender;
@Basic
@Column(name = "is_delete")
private Integer isDelete;
@Basic
@Column(name = "create_time",updatable = false,nullable = false)
@CreatedDate
private Date createTime;
@Basic
@Column(name = "modify_time",nullable = false)
@LastModifiedDate
private Date modifyTime;
@Basic
@Column(name = "avatar")
private String avatar;
各个注解的作用:
@Entity: 表示该类是一个实体类,需要映射到数据库中的表。
@Table(name = “user”, schema = “my_admin”): 指定该实体类对应的数据库表的名称和所属的数据库模式。
@Where(clause = “is_delete=0”): 定义了一个查询条件,表示只查询is_delete字段为0的记录。
@DynamicInsert: 在执行插入操作时,动态生成SQL语句,只插入非null字段。
@DynamicUpdate: 在执行更新操作时,动态生成SQL语句,只更新非null字段。
@EntityListeners(AuditingEntityListener.class): 指定该实体类使用AuditingEntityListener监听器,用于自动填充创建时间和修改时间字段。
@Getter: 自动生成getter方法。
@Setter: 自动生成setter方法。
@Id: 标识该字段为主键。
@Column(name = “id”): 指定该字段对应数据库表中的列名。
@GeneratedValue(generator = “system-uuid”): 指定该字段的值由系统生成,使用了名为system-uuid的生成器。
@GenericGenerator(name = “system-uuid”,strategy = “uuid”): 定义了一个名为system-uuid的生成器,使用uuid策略生成唯一标识。
@Basic: 表示该字段是一个基本字段。
@CreatedDate: 在插入数据时,自动填充该字段为当前时间。
@LastModifiedDate: 在更新数据时,自动更新该字段为当前时间。
@Basic: 表示该字段是一个基本字段。
@Column(name = “username”): 指定该字段对应数据库表中的列名。
Repository
创建一个接口,继承自JpaRepository或其子接口,用于定义对实体类进行增删改查的方法。可以自定义方法名,Spring Data JPA会根据方法名自动生成对应的SQL查询语句。
Spring Data JPA通过方法名解析器(Method Name Resolver)来解析方法名,并根据方法名生成对应的SQL查询语句。方法名解析器会根据一定的规则解析方法名,提取出方法名中的关键词和条件,并根据这些关键词和条件生成相应的查询语句。
方法名解析器的规则如下:
-
根据方法名前缀解析操作类型:
- find / read:查询操作。
- count:统计操作。
- exists:判断记录是否存在操作。
- delete:删除操作。
-
根据方法名中的关键词解析查询条件:
- And:使用And连接多个条件。
- Or:使用Or连接多个条件。
- Between:范围查询。
- LessThan / LessThanEqual:小于 / 小于等于查询。
- GreaterThan / GreaterThanEqual:大于 / 大于等于查询。
- Like:模糊查询。
- Not / IsNot:否定查询。
- In:集合查询。
- NotIn:集合不包含查询。
- OrderBy:排序查询。
-
根据方法名中的属性名解析查询字段:
- 根据属性名解析查询字段。
通过以上规则,Spring Data JPA可以根据方法名自动生成对应的SQL查询语句。例如,根据方法名findByUsernameAndPassword(String username, String password)
,Spring Data JPA会解析出查询条件为username
和password
,并生成对应的SQL查询语句SELECT * FROM user WHERE username = ? AND password = ?
。
需要注意的是,方法名解析器只能解析一部分简单的查询条件,对于复杂的查询条件或者需要自定义SQL语句的情况,可以使用@Query注解或自定义Repository实现来实现更复杂的查询操作。
@Repository
public interface UserRepository extends JpaRepository<UserEntity,String>, JpaSpecificationExecutor<UserEntity> {
@Override
@Modifying
@Query("update UserEntity u set u.isDelete = 1 where u.id = :id")
void deleteById(@Param("id") String id);
}
这里我自定义了一个根据id逻辑删除数据的方法,使用@Query注解自定义了SQL语句。
Service
基本的增删改查:
/**
* 根据id逻辑删除用户信息
* @param id
*/
@Override
public void removeUserById(String id) {
userRepository.deleteById(id);
}
/**
* 保存用户
* @param user
*/
@Override
public void saveUser(UserEntity user) {
userRepository.save(user);
}
/**
* 根据id查询用户信息
* @param id
* @return
*/
@Override
public UserEntity queryUserById(String id) {
Optional<UserEntity> optional = userRepository.findById(id);
if (optional.isPresent()){
UserEntity user= optional.get();
return user;
}else {
return null;
}
}
/**
* 更新用户信息
* @param user
*/
@Override
public void updateUser(UserEntity user) {
Optional<UserEntity> optional = userRepository.findById(user.getId());
if (optional.isPresent()){
UserEntity dbInfo = optional.get();
BeanUtils.copyProperties(user, dbInfo);
userRepository.save(dbInfo);
}
}
根据ID查询数据(findById)
其中根据ID查询方法findById(id)返回的是一个Optional对象,表示查询结果可能存在也可能不存在。我们使用Optional.isPresent()方法判断查询结果是否存在。
如果查询结果存在,通过调用optional.get()方法获取查询到的用户实体对象,并将其赋值给user变量。
更新数据 (update)
更新方法是先查出数据中需要更新数据的对象,使用BeanUtils工具类的copyProperties进行属性对拷,然后调用save方法保存更新后的数据。
在Spring Data JPA中,使用save()方法进行更新操作的原因是因为save()方法在实际执行时会根据传入的实体对象的主键(ID)来判断是新增还是更新操作。
当调用save()方法时,它会首先检查传入的实体对象是否有主键。如果实体对象的主键存在(即主键不为null),则会执行更新操作;如果主键不存在(即主键为null),则会执行新增操作。
具体来说,save()方法会根据传入的实体对象的主键进行判断:
如果主键存在,即数据库中已经存在该实体对象对应的记录,则会执行更新操作,将传入的实体对象的属性值更新到数据库中的记录。
如果主键不存在,即数据库中不存在该实体对象对应的记录,则会执行新增操作,将传入的实体对象插入到数据库中。
这种设计的好处是,开发人员无需手动判断是执行更新还是新增操作,只需要调用save()方法即可。Spring Data JPA会根据实体对象的主键自动判断并执行相应的操作。这样可以简化开发流程,并提高代码的可读性和可维护性。
需要注意的是,在执行更新操作时,save()方法会将传入的实体对象的所有属性值更新到数据库中的记录,即使某些属性值没有发生变化。如果只想更新部分属性,可以先查询数据库中的记录,再手动更新需要修改的属性值,然后调用save()方法保存更新后的实体对象。
分页条件查询
@Override
public Page<UserEntity> pageFindAll(Integer page, Integer size, UserQuery userQuery) {
String realName = userQuery.getRealName();
String username = userQuery.getUsername();
String gender = userQuery.getGender();
String begin = userQuery.getBegin();
String end = userQuery.getEnd();
Pageable pageable = PageRequest.of(page, size);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Specification<UserEntity> specification = (root,query,criteriaBuilder)-> {
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(realName)){
predicates.add(criteriaBuilder.like(root.get("realName"), "%"+realName+"%"));
}
if (StringUtils.isNotBlank(username)){
predicates.add(criteriaBuilder.like(root.get("username"), "%"+username+"%"));
}
if (StringUtils.isNotBlank(gender)){
predicates.add(criteriaBuilder.equal(root.get("gender"), gender));
}
if (StringUtils.isNotBlank(begin)){
try {
Date beginDate = sdf.parse(begin);
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime"), beginDate));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
if (StringUtils.isNotBlank(end)){
try {
Date endDate = sdf.parse(end);
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createTime"), endDate));
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
return userRepository.findAll(specification,pageable);
}
这个方法是根据查询条件查询用户信息的方法,使用了Spring Data JPA的动态查询功能。
-
首先,根据传入的查询条件,获取
realName
、username
、gender
、begin
和end
等参数,这些参数用于构建查询条件。 -
创建一个
Pageable
对象,用于指定查询结果的分页信息,PageRequest.of(page, size)
方法根据传入的页码和每页大小创建Pageable
对象。 -
创建一个
SimpleDateFormat
对象,用于将传入的日期字符串转换为Date
对象。 -
创建一个
Specification
对象,该对象用于构建动态查询条件。Specification
接口是Spring Data JPA提供的用于构建动态查询条件的接口,它定义了一个toPredicate()
方法,用于构建查询条件的逻辑。在这个方法中,使用Lambda表达式实现了toPredicate()
方法。 -
在
toPredicate()
方法中,首先创建一个空的List<Predicate>
集合,用于存储查询条件。 -
根据传入的查询条件,使用
StringUtils.isNotBlank()
方法判断查询条件是否为空或空字符串,如果不为空,则根据查询条件构建相应的查询条件表达式,并将其添加到predicates
集合中。 -
对于
realName
和username
字段,使用criteriaBuilder.like()
方法构建模糊查询条件,使用root.get("realName")
和root.get("username")
获取实体对象的属性路径。 -
对于
gender
字段,使用criteriaBuilder.equal()
方法构建精确查询条件,使用root.get("gender")
获取实体对象的属性路径。 -
对于
begin
和end
字段,首先使用SimpleDateFormat
将传入的日期字符串转换为Date
对象,然后使用criteriaBuilder.greaterThanOrEqualTo()
和criteriaBuilder.lessThanOrEqualTo()
方法构建大于等于和小于等于的查询条件,使用root.get("createTime")
获取实体对象的属性路径。 -
最后,使用
criteriaBuilder.and()
方法将所有的查询条件连接起来,并将查询条件数组转换为Predicate
对象。 -
调用
userRepository.findAll(specification, pageable)
方法执行查询操作,传入动态查询条件和分页信息,返回查询结果。
这个方法的作用是根据传入的查询条件动态构建查询条件,并使用分页信息进行分页查询。返回符合查询条件的用户信息列表。
springboot启动类
@SpringBootApplication
@EnableJpaAuditing //启用 JPA 的审计功能
public class MyAdminApplication {
public static void main(String[] args) {
SpringApplication.run(MyAdminApplication.class, args);
}
}
@EnableJpaAuditing
注解是用于启用JPA的审计功能的注解。
JPA的审计功能是指在实体对象的插入、更新、删除等操作中,自动记录相关的审计信息,例如创建时间、创建人、修改时间、修改人等。通过启用JPA的审计功能,可以方便地跟踪和记录实体对象的变更历史,便于后续的审计、追溯和分析。
具体来说,@EnableJpaAuditing
注解的作用包括:
-
启用审计功能:通过在应用程序的配置类上添加
@EnableJpaAuditing
注解,告诉Spring Boot启用JPA的审计功能。 -
自动填充审计字段:启用审计功能后,JPA会自动填充实体对象的审计字段,例如
@CreatedBy
、@CreatedDate
、@LastModifiedBy
和@LastModifiedDate
等注解修饰的字段。 -
自动记录审计信息:在实体对象的插入、更新、删除等操作中,JPA会自动记录相关的审计信息,例如创建时间、创建人、修改时间、修改人等。这些信息会自动保存到数据库中,无需手动处理。
通过启用JPA的审计功能,可以方便地记录实体对象的变更历史,提高数据的可追溯性和可审计性。同时,也减少了开发人员手动处理审计信息的工作量,提高了开发效率。
需要注意的是,为了使@EnableJpaAuditing
注解生效,还需要在实体类中使用@EntityListeners(AuditingEntityListener.class)
注解来指定审计监听器。审计监听器负责在实体对象的插入、更新、删除等操作中自动填充审计字段。