JPA / Hibernate

1. JPA 和 Hibernate 有什么区别?

JPA 是 Java 官方提出的一套 ORM 规范,它只定义了实体映射、关系管理、查询接口等标准,不包含具体实现。Hibernate 是对 JPA 规范的最常用实现,提供了完整的 ORM 功能,并扩展了许多 JPA 没有的高级特性。而 Spring Data JPA 是 Spring 提供的对 JPA 的封装,它简化了 Repository 层的开发,支持方法名自动派生查询。在 Spring Boot 项目中,Spring Data JPA 默认使用 Hibernate 作为底层 JPA 实现。

2. JPA 常见注解作用?

JPA 通过 @Entity 将类标记为持久化实体,使用 @Id@GeneratedValue 标识主键,使用 @Column 映射字段,并通过 @OneToMany@ManyToOne 等注解描述实体间关系。还可以通过 @Temporal@Transient 控制字段行为。实际项目中常结合 Hibernate 提供的扩展注解实现时间自动填充等高级功能。

注解

作用

示例

@Entity

声明这是一个实体类,对应数据库表

@Entity

@Table(name="user")

映射表名(可选)

@Table(name="user")

@Id

标记主键字段

@Id

@GeneratedValue(strategy=...)

主键生成策略

@GeneratedValue(strategy = GenerationType.IDENTITY)

策略

说明

IDENTITY

自增(MySQL 常用)

SEQUENCE

数据库序列(Oracle 常用)

TABLE

使用一张表生成主键

AUTO

自动选择适合的策略(默认)

注解

作用

示例

@Column(name="username")

映射字段名(可选)

@Column(nullable = false, length = 50)

@Transient

忽略此字段,不参与映射

不入库的字段

@Lob

大字段(如 text、blob)

文本/二进制

@Temporal

映射日期时间类型(Date)

@Temporal(TemporalType.DATE)

映射类型(Java Date)

TemporalType.DATE

只保存日期(yyyy-MM-dd)

TemporalType.TIME

只保存时间(HH:mm:ss)

TemporalType.TIMESTAMP

保存日期+时间

注解

关系类型

对应关系

@OneToOne

一对一

用户 - 身份证

@OneToMany

一对多

用户 - 订单

@ManyToOne

多对一

订单 - 用户

@ManyToMany

多对多

用户 - 角色

@JoinColumn

指定外键列名

单向关系中用

@JoinTable

中间表配置

多对多时使用

mappedBy

指定关系被维护方

用于双向关系

cascade

联级操作

PERSIST、REMOVE 等

fetch

加载策略

LAZY / EAGER

类型

含义说明

PERSIST

保存时级联保存(save()

MERGE

合并更新时级联更新(merge()

REMOVE

删除时级联删除(delete()

REFRESH

刷新实体状态时也刷新关联对象

DETACH

分离实体时也分离关联对象

ALL

包含以上所有操作(常用)

注解

作用

@PrePersist

插入前执行

@PostPersist

插入后执行

@PreUpdate

更新前执行

@PostUpdate

更新后执行

@PreRemove

删除前执行

@PostRemove

删除后执行

@PostLoad

加载后执行

3. Spring Data JPA 如何实现自定义 SQL?

Spring Data JPA 实现自定义 SQL 的常用方法是通过 @Query 注解编写 JPQL 或原生 SQL,并配合 @Modifying 实现更新删除操作。对于更复杂或动态查询,可以通过自定义 Repository 实现类,利用 EntityManager 执行原生或 JPQL 查询,或者使用 JPA Criteria API 结合 Specification 来动态构造查询条件。

1. 使用 @Query 注解写 JPQL 或原生 SQL

public interface UserRepository extends JpaRepository<User, Long> {// JPQL 查询(面向实体类和属性名)@Query("select u from User u where u.name = ?1")List<User> findByNameJPQL(String name);// 原生 SQL 查询,nativeQuery = true@Query(value = "select * from user where name = ?1", nativeQuery = true)List<User> findByNameNative(String name);
}

2. 使用命名参数(推荐,代码更易读)

@Query("select u from User u where u.name = :name and u.age > :age")
List<User> findByNameAndAge(@Param("name") String name, @Param("age") Integer age);

3. 定义更新或删除操作(需要加 @Modifying

@Modifying
@Query("update User u set u.status = :status where u.id = :id")
int updateStatus(@Param("status") Integer status, @Param("id") Long id);

注意:执行更新或删除时,方法上必须加 @Modifying 注解,并且调用前通常要加事务(@Transactional)。

4.设计一个 SqlServiceImpl 作为 SQL 服务组件,内部封装基于 EntityManager 的通用查询和更新方法

@Service
public class SqlServiceImpl implements SqlService {@PersistenceContextprivate EntityManager em;@Overridepublic <T> List<T> queryList(String sql, Map<String, Object> params, Class<T> resultClass) {Query query = em.createNativeQuery(sql, resultClass);setParams(query, params);return query.getResultList();}@Overridepublic int executeUpdate(String sql, Map<String, Object> params) {Query query = em.createNativeQuery(sql);setParams(query, params);return query.executeUpdate();}private void setParams(Query query, Map<String, Object> params) {if (params != null) {params.forEach(query::setParameter);}}
}

4. JPA 的缓存机制?MyBatis 的缓存区别在哪

JPA 默认支持一级缓存,绑定在 EntityManager 生命周期内,避免重复查询;可选开启二级缓存,在多个 EntityManager 之间共享实体,提高性能。常用的二级缓存实现包括 EhCache、Redis 等。对于跨事务的高频读取场景,通过二级缓存可以显著减少数据库压力。但需注意缓存一致性和适配分布式场景的问题。

JPA 的二级缓存和 MyBatis 的二级缓存在原理上类似,都是跨 Session 的共享缓存机制,目的是避免重复查询、提升性能。它们分别绑定在 EntityManager 和 SqlSession 上,区别在于 JPA 是基于实体对象的缓存,而 MyBatis 更偏向 SQL 结果级别。二者都要求结合缓存一致性策略合理使用,否则可能造成数据不一致。

JPA 的实体缓存由于基于实体主键,缓存粒度细,命中率高,因此访问效率通常优于 MyBatis 结果缓存。MyBatis 结果缓存依赖完全相同的 SQL 和参数,命中率相对较低,且缓存的结果需要反序列化成对象,性能略逊一筹。但 MyBatis 缓存更灵活,适合复杂查询场景。实际选择要结合业务特点。

MyBatis 在执行写操作后会直接清除当前 SqlSession 或 Mapper 对应的缓存区域,防止读取脏数据,但不会维护缓存一致性。而 JPA 则是基于实体生命周期管理缓存,CUD 操作后会自动更新缓存中对应实体的状态,确保缓存始终与数据库一致。JPA 缓存更新更智能,但控制相对复杂;MyBatis 缓存简单粗暴,控制更灵活。

5. JPA 如何实现分页?

在 JPA 中分页可以通过 JpaRepository 接口提供的 findAll(Pageable pageable) 方法快速实现,也可以结合自定义 @Query 和 Pageable 参数实现分页。对于更复杂的查询,可使用 EntityManager 执行原生 SQL 并手动设置 setFirstResult()setMaxResults() 进行分页。整体上,JPA 分页底层是通过 limit offset 实现的,支持多种方式。

Page<T> findAll(Pageable pageable);Pageable pageable = PageRequest.of(0, 10, Sort.by("createTime").descending());
Page<User> userPage = userRepository.findAll(pageable);@Query("SELECT u FROM User u WHERE u.status = :status")
Page<User> findByStatus(@Param("status") Integer status, Pageable pageable);Page<User> page = userRepository.findByStatus(1, PageRequest.of(1, 20));Query query = entityManager.createNativeQuery("SELECT * FROM user WHERE status = ?", User.class);
query.setParameter(1, 1);
query.setFirstResult(0); // offset
query.setMaxResults(10); // page size
List<User> resultList = query.getResultList();

6. 什么是 N+1 查询问题?JPA 如何防止 N+1 查询问题?

N+1 查询问题是指:你执行了 1 条查询语句获取了主实体列表,但为了获取每个主实体的关联数据,又触发了 N 条额外的查询,共执行 N+1 次查询。
在实际项目中,我们采用 JPA 管理实体关系,但在复杂查询场景中做了区分:

    • 对于一对多的单集合,我们使用 @EntityGraphJOIN FETCH 一次性加载
    • 对于多个集合的场景,为了避免笛卡尔积,我们拆分为多条 SQL 查询并在服务层聚合
    • 一对一可以直接 fetch,多对多则更建议拆分或优化结构处理
      这种方式兼顾了查询性能与代码结构,是我们在 JPA 使用上的一套标准规范。

EntityGraph 和 JOIN FETCH 能有效解决一对一和一对多的单集合场景。但当面对多个集合或多对多结构时,使用多个 JOIN FETCH 会导致笛卡尔积,带来严重性能问题。我们会采用分批拉取子表数据并按主键分组组装的方式替代,避免了真正的 N+1 查询问题,同时保持数据结构清晰和查询效率。

N+1 查询问题广泛存在于 ORM 框架中,虽然最常出现在懒加载的查询场景中,但更新(saveAll)、插入(saveAll)、删除操作中如果未启用批处理,同样也可能出现 N+1 的问题。因此我们通常会在 JPA 配置中启用 hibernate.jdbc.batch_size 等批处理参数,同时在逻辑层避免逐条操作,通过批处理语句或一次性提交操作来优化性能。而且saveAll() 和 JPA 的自动更新(dirty checking)本质上是针对每个实体单独生成一条 UPDATE SQL,即使开启了 Hibernate 的 JDBC 批量功能,也只是把多条 SQL 放到同一个批次网络发送,数据库还是执行多条 UPDATE,只不过减少了网络开销。

  • 如果你想真正做到“一条 SQL 更新多条数据”,必须使用 JPQL 的批量更新语句或者原生 SQL,比如:
@Modifying
@Query("UPDATE User u SET u.status = :status WHERE u.age > :age")
int bulkUpdate(@Param("status") Integer status, @Param("age") Integer age);

这条语句就是一条 SQL 在数据库执行,效率最高。

在 Spring Data JPA 中,我们通常使用 deleteByIdIn(List<Long> ids) 实现批量删除,这种方式底层执行的是一条 DELETE IN 语句,效率高且不加载实体,适合不需要触发实体事件的场景。相比手动遍历删除,它更适合大批量数据删除,尤其是在没有复杂业务逻辑时。

7. JPA/Hibernate 的懒加载和立即加载?

在 JPA 中,懒加载和立即加载控制着关联实体什么时候加载。懒加载可以在真正访问字段时再查询,提升查询效率,是默认行为;而立即加载则在主表查询时立即加载全部关联数据,使用方便但可能带来性能问题。我们通常对一对多、多对多使用懒加载,并结合 EntityGraph、Join Fetch 等方式显式控制加载时机,避免产生 N+1 查询或懒加载异常。

8. JPA 如何实现事务管理?

在 JPA 中事务由 Spring 统一管理,我们通常使用 @Transactional 来声明事务边界。Spring 会为事务方法创建代理,在方法执行前开启事务,方法正常结束提交事务,异常则自动回滚。默认情况下只对 RuntimeException 回滚,若需处理受检异常需要手动指定。JPA 操作必须在事务中执行,否则会抛出 TransactionRequiredException,我们也可以通过事务传播机制控制方法嵌套的事务行为。

9. JPA 高性能分页与大数据量优化策略

默认 JPA 分页基于 LIMIT + OFFSET 实现,简单易用,但底层使用的是跳过(skip scan),越往后性能越差。性能线性下降!当数据量大、页码高时性能严重下降。为了解决这一问题,我们使用覆盖索引 + where id > ? 进行 游标分页(Keyset Pagination)实现高效翻页;此外还可通过投影查询减少字段,或结合数据库索引与分表策略优化分页性能。实际项目中会根据场景综合选择分页策略。

10. JPA + Redis 缓存实战方案

JPA 默认开启了一级缓存,基于 EntityManager 实例,在事务内缓存同一个实体对象,避免重复查询数据库。而二级缓存是 Hibernate 的扩展功能,用于不同 EntityManager 实例之间共享实体缓存,需要通过注解和配置文件显式启用。 常通过 EhCache、Redis 等作为缓存实现。但考虑到其复杂性和集群一致性问题,实际项目中建议优先使用 Spring Cache + Redis 替代原生方案。 为了实现分布式共享,我们通常结合 Redis 或 Caffeine 等缓存框架替代原生二级缓存,搭配 Spring Cache 更灵活。

实际项目中我们通常采用 Spring Cache 来处理接口层的缓存,比如常规的查询接口、字典表、配置项等,这样可以借助注解简化代码。而对于 Redis 的高级数据结构操作(如排行榜、限流、锁等),则通过 RedisTemplate 实现。在我们项目中,我们进一步封装了一个统一的缓存组件,底层既支持 Spring Cache,也支持 RedisTemplate,便于统一 TTL 策略、命名规则与缓存失效控制。

11. 为什么已经有了 JPA,还要用 MyBatis?

实际项目中我们既使用过 JPA,也使用过 MyBatis,一般来说业务模型结构清晰、查询简单的模块我们使用 Spring Data JPA 实现,利用它的自动查询机制提升开发效率;而对于多表关联复杂、SQL 调优要求高的模块,我们使用 MyBatis 或 MyBatis-Plus 来手动编写 SQL,更加灵活可控。在一些大型项目中,两者并存是非常常见的。

12. JPA Entity 生命周期

状态

说明

触发方式/操作

是否持久化到数据库

瞬时态(Transient)

实例刚创建,未关联到持久化上下文

new Entity()

;未调用 persist()

托管态(Managed / Persistent)

实体被 EntityManager 管理,处于持久化上下文中

调用 persist()

find()

查出对象

游离态(Detached)

实体曾被管理,但现在已脱离持久化上下文

调用 detach()

,事务结束,EntityManager 关闭

否(但数据库有记录)

删除态(Removed)

实体被标记为删除,等待同步到数据库

调用 remove()

是(删除操作)

当前状态

操作

结果状态

瞬时态

persist(entity)

托管态

托管态

remove(entity)

删除态

托管态

detach(entity)

游离态

游离态

merge(entity)

托管态

删除态

事务提交/刷新

数据库中删除

游离态/瞬时态

不受 EntityManager 管理

13. JPA 如何联表查询?

1. 基于实体关联映射的查询

JPA 支持通过实体间的关系(如 @OneToMany@ManyToOne@ManyToMany@OneToOne)来自动联表查询。示例:假设 OrderCustomer 关联,Order 有个 customer 属性:

@Entity
public class Order {@ManyToOne@JoinColumn(name = "customer_id")private Customer customer;// other fields...
}

查询所有订单及其客户:

List<Order> orders = entityManager.createQuery("SELECT o FROM Order o JOIN FETCH o.customer", Order.class).getResultList();

这里 JOIN FETCH 用于立即加载客户,避免 N+1 查询。

2. 使用 JPQL 显式写联表查询

你也可以写类似 SQL 的 JPQL 联表查询:

SELECT o, c FROM Order o JOIN o.customer c WHERE c.status = :status

也可以写普通 JOIN 或者 LEFT JOIN

14. 什么是全自动 ORM 与 半自动 ORM?

全自动 ORM 是指框架根据实体类和注解自动生成所有 SQL 语句,开发者无需手写 SQL,适合简单的 CRUD 和业务场景,代表技术如 JPA、Spring Data JPA。优点是开发效率高,但对复杂查询和性能调优支持有限。

半自动 ORM 则是开发者需要手写 SQL 或 XML 映射来完成复杂查询,框架负责对象映射和部分 CRUD 操作,代表技术如 MyBatis。它灵活度高,适合复杂业务,但开发和维护成本较大。

总结来说,全自动 ORM 适合快速开发和简单场景,半自动 ORM 更适合复杂查询和性能优化需求。

15. JPQL 和 SQL 的区别

JPQL 是 JPA 定义的面向对象查询语言,操作的是实体类和属性,不直接操作数据库表,语法类似 SQL,但用实体名和属性名替代表名和字段名。JPQL 具有数据库无关性,能方便地查询实体间的关系和继承结构。

而 SQL 是数据库的标准查询语言,直接操作数据库表和字段,语法依赖具体数据库方言,功能更强大,适合复杂查询和性能调优。

总结来说,JPQL 更适合 ORM 场景的对象查询,SQL 则适合原生复杂查询。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89203.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89203.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kibana显示未准备就绪

kibana显示未准备就绪 最近在研究新版本的ELK&#xff08;Elasticsearch, Logstash, Kibana&#xff09;栈时遇到了一个问题&#xff1a;虽然服务器本身能够访问ELK服务&#xff0c;但通过浏览器尝试访问时却无法成功。这里我将分享一些可能的排查步骤和解决方案。连接es的地址…

语音对话秒译 + 视频悬浮字 + 相机即拍即译:ViiTor 如何破局跨语言场景?

在跨语言信息获取场景中&#xff0c;语言壁垒常导致效率降低。ViiTor Translate 试图通过 “场景化功能布局” &#xff0c;覆盖 语音、视频、图像、文本 四大维度翻译需求。以下基于产品功能展示&#xff0c;拆解其核心能力&#xff1a; 1. 实时语音对话翻译&#xff1a;打破交…

国内第一梯队终端安全产品解析:技术与场景实践

国内终端安全市场的第一梯队产品&#xff0c;通常具备技术领先性、场景覆盖度和规模化落地能力。结合 2025 年最新行业动态与实战案例&#xff0c;以下从技术架构、核心能力和典型应用三个维度&#xff0c;解析当前市场的头部产品及其差异化价值。一、技术架构与市场格局国内终…

FTP 备份,一种更安全的备份方式

备份数据后最重要的任务是确保备份安全存储&#xff0c;最好是异地存储。您可以通过物理方式将备份介质&#xff08;例如磁带和 CD/DVD&#xff09;移动到异地位置。这是一个乏味、耗时、不方便且不可靠的方式。更简单的解决方案是通过 FTP 备份到保存在异地的服务器。什么是 F…

理解 HTTP POST 请求中的 json 和 data 参数

在使用 Python 发送 HTTP POST 请求时&#xff08;无论是使用 requests 还是 aiohttp&#xff09;&#xff0c;json 和 data 参数有明确的区别和使用场景。理解这些区别对正确构建请求至关重要。关键区别特性json 参数data 参数内容类型自动设置为 application/json需要手动设置…

C#反射机制与Activator.CreateInstance

本文仅作为参考大佬们文章的总结。 反射是C#和.NET框架中一项强大的功能&#xff0c;允许程序在运行时检查、创建和操作类型、方法、属性等元数据。作为反射机制的核心组件&#xff0c;Activator.CreateInstance提供了动态实例化对象的灵活方式。本文将全面剖析C#反射的原理、…

Linux的用户和用户组与权限解析、环境变量说明与配置、sudo配置解析和使用

一、Linux的用户及用户组与权限 1.1、Linux的用户和用户组内容介绍 Linux的用户角色分类序号Linux的用户角色说明1超级用户拥有对系统的最高管理权限&#xff0c;可执行任意操作&#xff0c;默认是root用户2普通用户只能对自己目录下的文件进行访问和修改&#xff0c;具有登录系…

图解LeetCode:79递归实现单词搜索

网格 (board): 单词搜索 中等 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻”…

2025 R3CTF

文章目录EvalgelistSilent Profit&#xff08;复现&#xff09;Evalgelist <?phpif (isset($_GET[input])) {echo <div class"output">;$filtered str_replace([$, (, ), , ", "", "", ":", "/", "!&…

WebView JSBridge 无响应问题排查实录 全流程定位桥接调用失效

在混合开发项目中&#xff0c;Web 页面与 Native 的通信桥梁——JSBridge&#xff0c;承担着极为关键的角色。它不仅让网页能调起原生功能&#xff08;分享、登录、拍照等&#xff09;&#xff0c;也支持原生传值、事件回调。 然而&#xff0c;当 JSBridge 调用“没有响应”、c…

前端构建工具 Webpack 5 的优化策略与高级配置

前端构建工具 Webpack 5 的优化策略与高级配置 当你的项目启动需要一分钟&#xff0c;或者每次热更新都像在“编译整个宇宙”时&#xff0c;你可能已经意识到了一个问题&#xff1a;前端构建性能&#xff0c;正成为开发效率的瓶颈。Webpack 作为现代前端开发的基石&#xff0c;…

tun2socks原理浅析

tun2socks 的原理是将TUN 设备上的IP 数据包转换为SOCKS 协议数据&#xff0c;然后通过SOCKS 代理服务器发送。简单来说&#xff0c;它利用TUN 设备模拟一个虚拟网络接口&#xff0c;将所有流经该接口的网络流量重定向到SOCKS 代理&#xff0c;从而实现流量的代理转发&#xff…

Go从入门到精通(22) - 一个简单web项目-统一日志输出

Go从入门到精通(21) - 一个简单web项目-统一日志输出 统一日志输出 文章目录Go从入门到精通(21) - 一个简单web项目-统一日志输出前言日志库横向对比zap 使用安装依赖创建日志配置修改主程序的日志在处理函数中使用日志日志示例控制台输出文件输出&#xff08;json&#xff09…

UI前端大数据处理新挑战:如何高效处理实时数据流?

hello宝子们...我们是艾斯视觉擅长ui设计和前端数字孪生、大数据、三维建模、三维动画10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩!一、引言&#xff1a;从 “批处理” 到 “流处理” 的前端革命当股票 APP 因每秒接收 10 万条行情数据…

【接口测试】08 Postman使用教程(带案例)

目录 一. Postman安装 二. Postman使用 1. 创建项目 2. 创建集合 3. 设置变量 4. 创建测试用例 5. 数据驱动测试 6. 接口关联 7. 断言和封装 8. 批量执行 9. 导出用例 10. 生成测试报告 一. Postman安装 PostMan——安装教程&#xff08;图文详解&#xff09;_postman安装教程-…

从springcloud-gateway了解同步和异步,webflux webMvc、共享变量

webMVC和webFlux 这是spring framework提供的两种不同的Web编程模型应用场景&#xff1a;用 WebMvc&#xff1a; 项目依赖 Servlet 生态、需要简单同步代码&#xff0c;或使用阻塞式数据库&#xff08;如 MySQL JDBC&#xff09;。用 WebFlux&#xff1a; 需要高并发&#xff…

如何在 Pytest 中调用其他用例返回的接口参数?

回答重点在 Pytest 中&#xff0c;我们可以通过使用共享夹具&#xff08;fixtures&#xff09;来调用和复用其他用例返回的接口参数。在 Pytest 中&#xff0c;fixtures 提供了一种灵活且有组织的方式来共享测试数据或对象。具体步骤如下&#xff1a;1&#xff09;首先&#xf…

倒计时熔断机制的出价逻辑

一、业务背景传统竞价机制中&#xff0c;“倒计时结束”是系统决定成交者的关键逻辑&#xff0c;但在实际中&#xff0c;最后3秒突然被抢价的情况极为常见&#xff0c;出现以下问题&#xff1a;用户投诉平台机制不公平&#xff1b;用户出价但未成交&#xff0c;产生争议订单&am…

未来手机会自动充电吗

未来手机实现‌全自动充电&#xff08;无需人为干预&#xff09;‌是技术发展的明确趋势&#xff0c;目前已有部分技术落地&#xff0c;但要达到“随时随地无感补电”&#xff0c;仍需突破以下关键领域&#xff1a;一、已实现的技术&#xff08;当下可用的“半自动”充电&#…

MySQL高级篇(二):深入理解数据库事务与MySQL锁机制

引言在现代数据库系统中&#xff0c;事务和锁机制是确保数据一致性和完整性的两大核心技术。无论是金融交易系统、电商平台还是企业级应用&#xff0c;都离不开这些基础功能的支持。本文将全面剖析数据库事务的四大特性&#xff0c;深入探讨MySQL中的各种锁机制&#xff0c;帮助…