Spring Data JPA以及JPQL等特性详细使用教程

背景:

最近项目用到了一种看着比较奇怪的写法,起初感觉非常不解,通过在网上学习后,逐渐理解了这些,将这块的实战跟各位同学做个分享。

上图:

接下来,我们就揭开这个神秘的面纱。

一、Spring Data JPA概述

Spring Data JPA 是 Spring Data 家族的一部分,旨在简化数据访问层(DAO)的开发。它基于 JPA(Java Persistence API) 规范,并通过提供 Repository 抽象,大幅减少样板代码。

  • 底层实现通常使用 Hibernate
  • 支持声明式查询(方法名推导)、JPQL、原生 SQL。
  • 提供分页、排序、审计、事务等开箱即用的功能

1.实战Demo

1.1添加依赖(Maven)

复制代码
<dependencies>
    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- 数据库驱动(以 MySQL 为例) -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

1.2 配置数据库(application.yml)

复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
    username: root
    password: root
  jpa:
    hibernate:
      ddl-auto: update  # 自动建表策略:create, create-drop, update, validate, none
    show-sql: true
    properties:
      hibernate:
        format_sql: true

1.3.实体类

复制代码
import javax.persistence.*;

@Entity
@Table(name = "users")
@Data
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(name = "full_name")
    private String fullName;

}

1.4.Repository 接口

复制代码
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

JpaRepository<T, ID> 提供了丰富的 CRUD 和分页方法。

或者这么写:

复制代码
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface BaseRepository<T> extends JpaRepository<T, Long>, JpaSpecificationExecutor<T> {
    default T getById(Long id) {
        //立即查询,而不是返回代理
        return findById(id).orElse(null);
    }
}

@Repository
public interface UserRepository extends BaseRepository<User> {

    @Query(value = "select c from test c " +
            "where c.isDel = 0 " +
            "and c.state = 98360 " +
            "and (:colis null or c.id not in :col) " +
            "order by c.createdAt desc")
    Page<User> findPageByConditions(@Param("col") List<Long> intentionIds, Pageable pageable);
}

2. 基本用法

2.1 常用方法(继承自 JpaRepository)

方法 说明
save(T entity) 保存或更新实体
findById(ID id) 根据主键查找
findAll() 查询所有
deleteById(ID id) 删除
count() 统计总数
existsById(ID id) 判断是否存在

2.2 分页与排序

复制代码
Pageable pageable = PageRequest.of(0, 10, Sort.by("email").ascending());
Page<User> users = userRepository.findAll(pageable);

二、JPQL

1.方法命名查询(Query Derivation)

Spring Data JPA 支持通过 方法名自动解析查询逻辑

1.1命名规则

格式:find[Distinct]...By[PropertyExpression][And|Or][PropertyExpression]...

复制代码
findFirstByServerNameAndIsDel(...)
关键词 含义
findBy 查询条件(必须以 find 开头)
first 只返回第一条记录(类似 LIMIT 1)
By 分隔符,后面接字段名
And 多个条件连接(AND)
字段名 实体类中的属性名(如 serverName, isDel

比如:

复制代码
// 根据 email 查找
User findByEmail(String email);

// 根据 email 或 fullName 查找(模糊)
List<User> findByEmailContainingOrFullNameContaining(String email, String name);

// 大于某个创建时间
List<User> findByCreatedAtAfter(LocalDateTime time);

// 分页 + 排序
Page<User> findByFullNameStartingWith(String prefix, Pageable pageable);

1.2 支持的关键字

关键字 示例 对应 JPQL
And / Or findByAgeAndName where x.age = ?1 and x.name = ?2
Between findByAgeBetween where x.age between ?1 and ?2
LessThan / GreaterThan findByAgeLessThan where x.age < ?1
Like / Containing findByNameContaining where x.name like %?1%
IsNull / IsNotNull findByEmailIsNull where x.email is null
OrderBy findByAgeOrderByCreateTimeDesc order by x.createTime desc

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

放个官方的文档,可以查看学习一下

写一个关于findByServerName的一系列方法

方法名 对应 SQL
findByServerName(String name) WHERE server_name = ?
findByServerNameAndIsDel(String name, Integer isDel) WHERE server_name = ? AND is_del = ?
findFirstByServerName(String name) SELECT ... WHERE server_name = ? ORDER BY id LIMIT 1
findByServerNameLike(String name) WHERE server_name LIKE ? (%name%)
findByServerNameContaining(String name) WHERE server_name LIKE %?%
findByServerNameIgnoreCase(String name) WHERE UPPER(server_name) = UPPER(?)
findByServerNameIn(List<String> names) WHERE server_name IN (?, ?, ...)

Spring Data JPA 通过"方法名命名规则"自动帮你生成 SQL,所以你不需要写 SQL 也能查数据!这是一种强大的约定式编程方式。

2.更新/删除操作(需加 @Modifying

复制代码
@Modifying
@Query("UPDATE User u SET u.email = :newEmail WHERE u.id = :id")
int updateUserEmail(@Param("id") Long id, @Param("newEmail") String newEmail);

3.原生 SQL 查询

使用 nativeQuery = true 可执行原生 SQL:

复制代码
@Query(value = "SELECT * FROM users WHERE email = ?", nativeQuery = true)
User findUserByEmailNative(String email);

原生 SQL 返回的字段需与实体类属性匹配

4.如果要查看自动生成的 SQL

复制代码
logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE

运行后就能发现sql了

三、事务管理

Spring Data JPA 默认在 Repository 方法上启用事务(只读事务用于查询,读写事务用于修改),如果需要自定义:

复制代码
@Service
@Transactional
public class UserService {

    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    @Transactional
    public User saveUser(User user) {
        return userRepository.save(user);
    }
}

总结:

  • 优先使用 方法命名查询,简洁清晰。
  • 复杂逻辑使用 JPQL,避免原生 SQL(除非必要)。
  • 使用 DTO/Projection 避免暴露整个实体。
  • 所有修改操作确保在 事务 中执行。
  • 合理使用 分页 避免内存溢出。
  • 开启 show-sqlformat_sql 便于调试。

先到这里,如有补充接着写。

https://blog.csdn.net/qq_32953079/article/details/79493541

https://blog.csdn.net/qq_32953079/article/details/79494955

隐藏问题处理:

今天发现在调用 userRepository.getById(id) 方法时,传了一个 不存在的 ID, 调试时发现:所有字段都是 null,但对象不为空,导致进了我的if了,经过排查,发现了如下情况:

复制代码
UserEntity userEntity = userRepository.getById(id);
if (userEntity!= null) {
    // 修改字段并保存
    chatEntity.setIsDel(1);
    chatRepository.save(userEntity);
}

核心原因:Hibernate 使用了代理对象(Proxy)机制

当你调用getById(id) 时,即使数据库里没有这个 ID 对应的数据,Hibernate 也会返回一个 代理对象(Proxy) ,它是一个"空壳"对象,看起来像实体,但实际上它的所有字段都为 null,但它本身不是 null,所以 != null 成立

这不是普通的 Java 对象,而是 Hibernate 的 懒加载代理(Lazy Loading Proxy)

正确处理方法:

复制代码
Optional<UseerEntity> optional = userRepository.findById(id);
if (optional.isPresent()) {
    UseerEntity entity = optional.get();
    entity.setIsDel(1);
    userRepository.save(entity);
} else {
    // 不存在,不做任何操作
    log.warn("ChatEntity not found for id: " + id);
}

findById() 返回的是 Optional<T>,如果查不到就返回 Optional.empty(),不会返回代理对象

我是因为代码里面写了好多这个getById(id)查询,我封装了一下,可供大家参考

复制代码
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface BaseRepository<T> extends JpaRepository<T, Long>, JpaSpecificationExecutor<T> {
    default T getById(Long id) {
        //立即查询,而不是返回代理
        return findById(id).orElse(null);
    }
}

补充getById() vs findById()

方法 返回类型 是否返回代理对象 是否能判断"不存在"
getById() T 是(代理) 不能(永远不为 null)
findById() Optional<T> 可以(isPresent()

放几个官方文档学习一下:

https://docs.spring.io/spring-data/jpa/reference/#jpa.query-methods.query-creation

https://docs.spring.io/spring-data/jpa/reference/

https://hibernate.org/orm/documentation/7.2/

https://www.objectdb.com/java/jpa/query/jpql/select

相关推荐
之歆20 分钟前
Spring AI入门到实战到原理源码-MCP
java·人工智能·spring
yangminlei40 分钟前
Spring Boot3集成LiteFlow!轻松实现业务流程编排
java·spring boot·后端
qq_3181215941 分钟前
互联网大厂Java面试故事:从Spring Boot到微服务架构的技术挑战与解答
java·spring boot·redis·spring cloud·微服务·面试·内容社区
计算机毕设VX:Fegn08951 小时前
计算机毕业设计|基于springboot + vue医院设备管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
J_liaty1 小时前
Spring Boot整合Nacos:从入门到精通
java·spring boot·后端·nacos
Mr__Miss1 小时前
保持redis和数据库一致性(双写一致性)
数据库·redis·spring
面汤放盐1 小时前
后端系统设计文档模板
后端
阿蒙Amon2 小时前
C#每日面试题-Array和ArrayList的区别
java·开发语言·c#
daidaidaiyu2 小时前
Spring IOC 源码学习 一文学习完整的加载流程
java·spring
Knight_AL2 小时前
Spring 事务传播行为 + 事务失效原因 + 传播行为为什么不用其他模式
数据库·sql·spring