news 2026/4/22 23:16:08

Spring Data JPA 实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Data JPA 实战指南

用过 MyBatis 再用 JPA,可能会觉得 JPA 很别扭——SQL 都不用写了,框架自动搞定。

但用久了会发现,JPA 写起来其实很爽,尤其单表操作,几乎不需要写 SQL。

基础配置

依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency>

配置

spring:datasource:url:jdbc:mysql://localhost:3306/test?useSSL=falseusername:rootpassword:123456jpa:hibernate:ddl-auto:update# 开发用 update,生产用 validateshow-sql:true# 打印 SQLproperties:hibernate:format_sql:true

ddl-auto几个选项:

  • update:自动更新表结构(不会删数据)
  • create:每次启动删表重建
  • validate:只验证,不改表
  • none:什么都不做

实体映射

基本映射

@Entity@Table(name="sys_user")@DatapublicclassUser{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;@Column(name="user_name",length=50,nullable=false)privateStringuserName;privateIntegerage;@Column(columnDefinition="varchar(20) default 'ACTIVE'")privateStringstatus;@Column(name="create_time")privateLocalDateTimecreateTime;@Transient// 不映射到数据库privateStringtempField;}

主键生成策略

@Id@GeneratedValue(strategy=GenerationType.IDENTITY)// MySQL 自增// 或@GeneratedValue(strategy=GenerationType.SEQUENCE)// Oracle、序列// 或@GeneratedValue(strategy=GenerationType.UUID)// UUIDprivateStringid;

Repository 基础 CRUD

继承JpaRepository就有基本的增删改查:

publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// 继承来的方法:// save(entity) - 保存或更新// findById(id) - 查询// deleteById(id) - 删除// count() - 计数// existsById(id) - 是否存在}
@Service@RequiredArgsConstructorpublicclassUserService{privatefinalUserRepositoryuserRepository;publicUsercreateUser(Stringname,Integerage){Useruser=newUser();user.setUserName(name);user.setAge(age);returnuserRepository.save(user);// 自动 insert}publicOptional<User>getUser(Longid){returnuserRepository.findById(id);// 自动 select}publicvoiddeleteUser(Longid){userRepository.deleteById(id);// 自动 delete}publicUserupdateUser(Longid,StringnewName){Useruser=userRepository.findById(id).orElseThrow(()->newRuntimeException("用户不存在"));user.setUserName(newName);returnuserRepository.save(user);// 自动 update}}

方法名查询(单表神器)

这是 JPA 最爽的地方——不用写 SQL,方法名就是查询

基础规则

publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// 等值查询:findBy + 字段名List<User>findByUserName(StringuserName);// → SELECT * FROM sys_user WHERE user_name = ?// 模糊查询:findBy + 字段名 + LikeList<User>findByUserNameLike(StringuserName);// → SELECT * FROM sys_user WHERE user_name LIKE ?// 多条件:findBy + 字段1 + And + 字段2List<User>findByUserNameAndAge(StringuserName,Integerage);// → SELECT * FROM sys_user WHERE user_name = ? AND age = ?// Or 条件List<User>findByUserNameOrAge(StringuserName,Integerage);// → SELECT * FROM sys_user WHERE user_name = ? OR age = ?}

常用关键词

// 大于/小于/等于List<User>findByAgeGreaterThan(Integerage);// age > ?List<User>findByAgeLessThan(Integerage);// age < ?List<User>findByAgeGreaterThanEqual(Integerage);// age >= ?List<User>findByStatusEquals(Stringstatus);// status = ?// BETWEEN 范围List<User>findByAgeBetween(Integermin,Integermax);// → WHERE age BETWEEN ? AND ?// IN 查询List<User>findByUserNameIn(List<String>names);// → WHERE user_name IN (?, ?, ?)// NULL 判断List<User>findByEmailIsNull();List<User>findByEmailIsNotNull();// True/FalseList<User>findByActiveTrue();// WHERE active = trueList<User>findByActiveFalse();// WHERE active = false// 排序List<User>findByStatusOrderByAgeDesc(Stringstatus);// → WHERE status = ? ORDER BY age DESC

分页和排序

// 分页:参数加 PageablePage<User>findByStatus(Stringstatus,Pageablepageable);// 排序:SortList<User>findByStatus(Stringstatus,Sortsort);// 使用@ServicepublicclassUserService{publicPage<User>getUserPage(intpage,intsize){Pageablepageable=PageRequest.of(page,size,Sort.by("age").descending());returnuserRepository.findByStatus("ACTIVE",pageable);}}

@Query 自定义查询

方法名解决不了的,用@Query

JPQL 查询

@Query("SELECT u FROM User u WHERE u.userName = ?1")UserfindByName(StringuserName);// 占位符 ?1、?2 按参数顺序@Query("SELECT u FROM User u WHERE u.userName = ?1 AND u.age > ?2")List<User>findByNameAndAge(Stringname,Integerage);// 命名参数(更清晰)@Query("SELECT u FROM User u WHERE u.userName = :name AND u.age > :age")List<User>findByNameAndAgeV2(@Param("name")Stringname,@Param("age")Integerage);

多表联查

假设 User 关联 Department:

@EntitypublicclassUser{@ManyToOne(fetch=FetchType.LAZY)@JoinColumn(name="dept_id")privateDepartmentdepartment;}// 查询用户并带出部门名称@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")UserfindByIdWithDepartment(@Param("id")Longid);

原生 SQL

@Query(value="SELECT * FROM sys_user WHERE user_name = :name",nativeQuery=true)UserfindByNameNative(@Param("name")Stringname);// 原生 SQL 分页(注意 countQuery)@Query(value="SELECT * FROM sys_user WHERE status = :status ORDER BY age DESC",countQuery="SELECT count(*) FROM sys_user WHERE status = :status",nativeQuery=true)Page<User>findByStatusPage(@Param("status")Stringstatus,Pageablepageable);

@Modifying 修改操作

@Modifying@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")intupdateStatus(@Param("id")Longid,@Param("status")Stringstatus);@Modifying@Query("DELETE FROM User u WHERE u.status = 'INACTIVE'")voiddeleteInactiveUsers();

记得在 Service 层加事务:

@TransactionalpublicvoidupdateStatus(Longid,Stringstatus){userRepository.updateStatus(id,status);}

一对多 / 多对一

实体定义

@Entity@Table(name="department")@DatapublicclassDepartment{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringname;@OneToMany(mappedBy="department",cascade=CascadeType.ALL,fetch=FetchType.LAZY)privateList<User>users;}@Entity@Table(name="sys_user")@DatapublicclassUser{@Id@GeneratedValue(strategy=GenerationType.IDENTITY)privateLongid;privateStringuserName;@ManyToOne(fetch=FetchType.LAZY)@JoinColumn(name="dept_id")privateDepartmentdepartment;}

查询

// 根据部门查用户List<User>findByDepartmentId(LongdeptId);// 根据用户查部门(直接访问属性就行)Useruser=userRepository.findById(id).orElseThrow(...);StringdeptName=user.getDepartment().getName();// 懒加载,会再查一次// 一次性查出来(解决 N+1)@Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id")UserfindByIdWithDept(@Param("id")Longid);

常见问题

1. 懒加载异常

// 报错:LazyInitializationExceptionUseruser=userRepository.findById(1L).orElseThrow(...);StringdeptName=user.getDepartment().getName();// session 已关闭

解决:在 Service 层加@Transactional,或者用JOIN FETCH一次性加载。

2. N+1 问题

// N+1:查 1 个用户,再查 N 次部门List<User>users=userRepository.findAll();// SELECT * FROM sys_user// SELECT * FROM department WHERE id = ?// SELECT * FROM department WHERE id = ?// ...

解决:用@EntityGraphJOIN FETCH

@EntityGraph(attributePaths={"department"})List<User>findAllWithDept();// 或@Query("SELECT u FROM User u JOIN FETCH u.department")List<User>findAllWithDept();

3. save 和 update 的区别

// save() - 如果 id 已存在就是 update,不存在就是 insertUseruser=newUser();user.setId(1L);// id 存在,变成 updateuser.setUserName("newName");userRepository.save(user);// UPDATE

总结

方式适用场景
继承JpaRepository基本 CRUD
方法名查询单表简单查询
@Query+ JPQL多表关联、复杂查询
@Query+ 原生 SQL特定数据库语法
JOIN FETCH解决懒加载和 N+1

JPA 最大的好处是不用写 SQL,单表操作特别爽。但多表关联和复杂查询,还是得用@Query。用久了会发现,JPA +@Query配合起来,效率比 MyBatis 高多了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 23:16:08

政务系统上线倒计时72小时!Docker镜像国产化扫描报告生成、漏洞修复、签名验签全流程(含工信部推荐工具链)

第一章&#xff1a;政务系统Docker国产化迁移的战略意义与合规基线政务信息系统正加速向安全可控、自主可信方向演进&#xff0c;Docker容器技术作为云原生基础设施的关键载体&#xff0c;其国产化迁移已从技术选型上升为国家战略部署的重要环节。该迁移不仅关乎算力底座的供应…

作者头像 李华
网站建设 2026/4/22 23:16:07

为什么你的虚拟线程没提速?——5个被90%团队忽略的关键配置:ForkJoinPool并行度、ScopedValue作用域、Loom调试开关…

第一章&#xff1a;虚拟线程性能失速的真相诊断虚拟线程&#xff08;Virtual Thread&#xff09;在 JDK 21 中作为 Project Loom 的核心特性&#xff0c;本应以极低调度开销支撑百万级并发&#xff0c;但实践中常出现吞吐骤降、延迟飙升甚至比传统线程更慢的“性能失速”现象。…

作者头像 李华
网站建设 2026/4/22 23:15:28

【硬核指南】嵌入式软件工程师-2025求职突围-从八股文到实战项目

1. 嵌入式软件工程师的2025求职突围战 2025年的嵌入式软件工程师求职市场&#xff0c;竞争将比以往更加激烈。作为一名从机械专业成功转型的过来人&#xff0c;我深刻理解跨专业求职者的焦虑与困惑。记得2024年秋招时&#xff0c;我用了35天时间拿到5个offer&#xff0c;最高年…

作者头像 李华
网站建设 2026/4/22 23:12:09

2026届最火的降AI率网站实测分析

Ai论文网站排名&#xff08;开题报告、文献综述、降aigc率、降重综合对比&#xff09; TOP1. 千笔AI TOP2. aipasspaper TOP3. 清北论文 TOP4. 豆包 TOP5. kimi TOP6. deepseek 为了降低AIGC那人工智能生成内容的检测通过率&#xff0c;能够从下面这些维度开始着手&#…

作者头像 李华