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

相关推荐
老马聊技术2 小时前
HBase完全分布式集群搭建详细教程
数据库·分布式·hbase
spencer_tseng2 小时前
Eclipse JDT Core for Java Code Formatter
java·ide·eclipse
秋邱2 小时前
Java 运算符与流程控制:全类型运算符用法 + 分支 / 循环语句实战
java·开发语言
万邦科技Lafite2 小时前
淘宝开放API批量上架商品操作指南(2025年最新版)
开发语言·数据库·python·开放api·电商开放平台·淘宝开放平台
IT_陈寒2 小时前
JavaScript 性能优化实战:7 个让你的应用提速 50%+ 的 V8 引擎技巧
前端·人工智能·后端
缺点内向2 小时前
C#:轻松实现Excel到TXT的转换
后端·c#·.net·excel
Chase_______2 小时前
【JAVA基础指南(二)】快速掌握流程控制
java·开发语言
Slow菜鸟2 小时前
Java基础架构设计(四)| 通用响应与异常处理(单体/分布式通用增强方案)
java·开发语言·分布式
Angletank2 小时前
SpringBoot中JPA组件深入查询业务实现
数据库·spring boot·后端·mysql