Spring Data JPA 和 JPA
Spring 中的 JPA 和 Hibernate 有什么区别?Spring Data JPA 能不能脱离 Hibernate 用?
JPA 是 Java 官方定义的 ORM 规范,只定义了接口和注解,不提供实现
Hibernate 是 JPA 规范的一个具体实现,是真正负责把 Java 对象和数据库表之间做映射、执行 SQL。而 Hibernate 的一个依赖就是 JDBC
Spring Data JPA 是 Spring 对 JPA 的再封装,不是 JPA 实现,底层默认用 Hibernate
而且 Spring Data JPA 能脱离 Hibernate 使用,因为 Spring Data JPA 是一个抽象层,支持任何符合 JPA 规范的实现框架
JPA 和 Hibernate 的”全自动 ORM”到底全自动在哪?代价是什么?
JPA 是按字段访问还是按属性访问?它怎么决定的?能混用吗?
Spring Data JPA 有哪几种写查询的方式?分别在什么场景用?方法名派生的关键字都有哪些规则?Criteria API 和 JPQL 有什么区别?各自优缺点?通过 JPA Criteria 和 JPQL 优化复杂查询的好处是什么,为什么不用方法名派生?
一套实体映射你常用的注解有哪些?JPA
中还有哪些常用的注解。详细说说它们。双向
@OneToMany/@ManyToOne
的维护方是哪一边?只设一边会怎样?
四种主键生成策略
AUTO / IDENTITY / SEQUENCE / TABLE
你项目里用哪个?为什么?你都是如何选择主键的?@SequenceGenerator
的 name 和 sequenceName 有什么区别?
实体类为什么必须有或最好有无参构造器?
spring.jpa.open-in-view
默认是 true,你怎么看?关掉之后要注意什么?
save()、persist()、merge()、saveOrUpdate()
有什么区别?为什么我 save() 一条新对象,日志里先出现一条
SELECT?
findById()、getById()、getOne()、getReferenceById()
有什么区别?
JPQL
里执行 update/delete 为什么必须加
@Modifying?你了解
@Modifying的具体行为吗?为什么 SQL
执行了内存里的对象却没变?
CascadeType
都有哪些?orphanRemoval 和 CascadeType.REMOVE
有什么区别?
@Embedded/@Embeddable
用在哪?@Embeddable + @ElementCollection
有什么区别?
JPA
实体为什么要小心重写
equals/hashCode?如何重写他们才能保证不出问题?
代表关联的相关注解了解吗?他们是如何实现关联的?双向关联里谁是维护方?为什么必须在一端配
mappedBy?
Sort
和 Pageable 一起用的时候,是 SQL 排序还是内存排序?
为什么用 JPA 时能不用实体查询就不用?我们都写 DTO 进行查询结果的封装。投影(Projection)和 DTO 查询怎么写?
saveAll()
是批量插入吗?saveAll() 为什么常常还是一条一条
INSERT,甚至每条前面还多一个 SELECT?
用
@Version
做乐观锁,它是怎么实现的?失败时你怎么处理?@Version
支持哪些类型?时间戳可以吗?
@Version
乐观锁有哪些失效场景?什么时候用乐观锁、什么时候用分布式锁?乐观锁冲突了怎么重试才安全?
悲观锁怎么写?@Lock(PESSIMISTIC_WRITE)
背后的 SQL 是什么?@Lock
加了但没生效,可能是什么原因?
JPA
审计 (JPA Auditing)
是怎么做的?@CreatedDate/@LastModifiedDate/@CreatedBy
靠什么生效?Auditing 和你们用 AOP +
自定义注解做的操作审计框架有什么区别?为什么不用同一个方案?
多数据源下
JPA 要配哪些东西?JpaTransactionManager 和
DataSourceTransactionManager 有什么区别?多数据源时
@Transactional 为什么要指定 transactionManager?
Hibernate
支持哪几种多租户隔离方式?Hibernate 6 的 @TenantId
是怎么工作的?有什么盲区?多租户下唯一索引、序列和二级缓存要注意什么?
事务管理
你怎么理解 Spring 事务?@Transactional 的失效场景有哪些?事务同步回调和事务事件怎么用?长事务怎么治理?
这道题一般会连着问,从用法问到失效,再问到事务和业务的边界怎么划,最后问到 NESTED 到底能不能用。
事务是怎么生效的?
@Transactional只是声明。工作的是 Spring AOP 代理
+ TransactionInterceptor 拦截器 +
PlatformTransactionManager
事务管理器三者的配合:
- 容器启动时,
AbstractAutoProxyCreator在postProcessAfterInitialization阶段发现 Bean 上有@Transactional,就为它创建代理(有接口用 JDK 动态代理,否则用 CGLIB),这也是为什么只有走代理的调用才会被拦截; TransactionAttributeSource解析注解得出一份事务属性(传播行为、隔离级别、回滚规则、超时、只读),只有真正的写方法才会开启事务,只读方法在readOnly=true时是优化而非不开启;- 调用时,
TransactionInterceptor先通过事务管理器拿到或新建事务,把连接绑到当前线程(TransactionSynchronizationManager的 ThreadLocal); - 目标方法执行完,由拦截器决定提交还是回滚。
失效场景
按照”为什么不生效”的根因分成四类,
第一类:调用没走代理
| 场景 | 原因 | 解决 |
|---|---|---|
自调用:同一个类里 A 方法调 B 方法,B 上有
@Transactional |
this 指向原始对象,不是代理对象,调用没经过拦截器 |
拆到另一个 Bean;自注入
@Autowired private XxxService self; 再
self.B();AopContext.currentProxy()(需
@EnableAspectJAutoProxy(exposeProxy=true));或者把事务方法下沉到独立的
Service |
| Bean 没被 Spring 管理 | 直接 new 出来的对象,或者没加
@Component/@Service 注册 |
交给容器管理,通过注入获取实例 |
自调用是所有 AOP 增强注解的通用陷阱:
@Async、@Cacheable、@Validated的自调用一样失效,区别只是”失效”的表现不同——@Transactional表现为不回滚,@Async表现为变成同步,@Cacheable表现为每次都查库。
第二类:代理没法拦截这个方法
| 场景 | 原因 | 解决 |
|---|---|---|
private 方法 |
代理无法拦截私有方法 | 改成 public |
final 方法 / final
类 |
CGLIB 靠继承生成子类,无法覆写 final | 去掉 final |
static 方法 |
静态方法属于类,不属于 Bean 实例 | 改成实例方法 |
protected 方法 |
JDK 代理只能拦接口方法;CGLIB 能拦 public/protected,但 Spring 的规则是只有 public 才生效 | 改成 public |
Spring Boot 2.x 起
spring.aop.proxy-target-class默认是true,也就是默认走 CGLIB,所以”类没有实现接口”这件事本身不是失效原因,这一点很多人答错。
第三类:异常没触发回滚
| 场景 | 原因 | 解决 |
|---|---|---|
rollbackFor 配错或没配 |
默认只对 RuntimeException 和 Error
回滚,受检异常不回滚 |
明确写
@Transactional(rollbackFor = Exception.class),业务异常继承
RuntimeException |
| 异常被 catch 吞掉 | 提交/回滚发生在方法返回之后,方法正常返回 → 提交 | 要么 throw 出去,要么在 catch 里
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); |
| 异常被包装成别的类型 | 包装成了受检异常,或者包装后不再匹配 rollbackFor |
统一异常体系,或者 rollbackFor 用父类 |
| 方法返回前就 return 了 | 逻辑分支提前返回,写操作在返回之后 | 检查代码路径,确保事务内的写操作都在同一个方法里完成 |
第四类:上下文不对
| 场景 | 原因 | 解决 |
|---|---|---|
多线程里:事务内 new Thread(...)
或丢线程池 |
事务是绑定在当前线程的,子线程拿到的是另一个连接,看不到未提交数据,子线程异常也不会让主线程回滚 | 全程单线程;必须异步的用
@TransactionalEventListener(AFTER_COMMIT) + @Async |
@Transactional 与 @Async
同方法 |
异步执行在新线程,事务上下文不会传递,等于没有事务 | 拆成两个方法,先提交事务再异步 |
| 多数据源用错事务管理器 | @Transactional 默认用容器里 @Primary
的那个管理器,操作非主库时事务不生效(自动提交) |
显式指定
@Transactional(transactionManager = "xxxTransactionManager") |
接口上的 @Transactional |
JDK 代理下注解在接口上不一定被代理类”继承” | 规范做法是写实现类的 public 方法上 |
TransactionSynchronization 与 @TransactionalEventListener 是什么关系?
这一块是事务回调
先说
TransactionSynchronization:每个事务都对应一个同步器集合,由
TransactionSynchronizationManager 维护(同样存在
ThreadLocal
里)。实现这个接口,就能在事务的生命周期上挂回调,顺序是:
| 回调方法 | 执行时机 | 典型用途 |
|---|---|---|
suspend() / resume() |
事务被挂起 / 恢复时(比如内层用了 REQUIRES_NEW) |
上下文的保存与还原 |
beforeCommit(status) |
提交之前,此时回滚仍然有效 | 补充校验、最终一致性检查 |
beforeCompletion() |
提交/回滚前最后一步 | 资源清理,别在这里写库 |
afterCommit() |
已提交之后 | 清缓存、发消息、发通知 |
afterCompletion(status) |
无论提交还是回滚都执行 | 清理 ThreadLocal、释放自定义资源、记录结果 |
注册方式有两种写法:
1 | // 写法一:手动注册(适合在事务方法内部临时挂一个回调) |
1 | /** |
@TransactionalEventListener 底层就是
TransactionSynchronization,Spring 提供了一个
TransactionSynchronizationEventAdapter(本质是一个
TransactionSynchronization
实现),在对应的回调里把事件发布出去。所以说:
@TransactionalEventListener是”事务同步器 + 事件机制”的封装,它让我们不必在每个业务方法里手写registerSynchronization,只要在事务提交前后发布一个事件就行,业务代码更干净;- 手动
TransactionSynchronization的优势是”就在当前方法里、不需要额外定义事件类”,适合一次性、跟当前方法强绑定的收尾逻辑。
再补充几个参数:
phase:BEFORE_COMMIT(默认值是排在后面的AFTER_COMMIT,注意默认是AFTER_COMMIT)、AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION;fallbackExecution = false(默认):没有活跃事务时监听器不执行。这就是为什么有的同学写了监听器却发现”没有事务时逻辑丢了”——要么给调用的地方加事务,要么显式设成true;@Async可以叠加使用,让回调在独立线程里跑,但要注意它拿不到原事务,也不能指望它回滚;AFTER_COMMIT里不能再写库:此时事务已结束,新写库是自动提交、没有事务保护,如果需要落库,应单独开一个REQUIRES_NEW事务,或者落成补偿任务。
为什么值得花力气用它?
因为它解决的是”事务内不能干的事”:发邮件、发
MQ、清缓存、调第三方。这些动作如果放在事务里,一旦后续回滚,副作用已经发生了,收不回来。用
AFTER_COMMIT
把它们挪到”确定提交之后”,才能保证”业务真的发生了,副作用才发生”。
长事务怎么治理?
长事务的危害是连锁的:连接被占住 → 连接池耗尽 → 其他请求全排队 → 服务雪崩;同时锁持有时间长会让别的会话锁等待甚至死锁,还会让 undo log / binlog 膨胀、主从延迟变大。所以说长事务不是慢一点的问题,而是要主动治理的对象。
1. 只读事务 readOnly = true
它有两个作用:一是给数据库一个明确的信号,MySQL 下可以省去 undo log 的写入、优化查询计划;二是给 Hibernate/JPA 一个信号,把 FlushMode 设为 MANUAL,避免脏检查触发无意义的 UPDATE。
注意两个坑:一是设了
readOnly = true之后执行增删改可能直接报错(MySQL 会拒绝,或者乐观锁失效),所以纯查询方法才加;二是readOnly不会传播给内层,内层如果自己是写事务,要按自己的语义声明。
2. 事务内不要做 RPC、不要循环里逐个写
这是长事务最常见的成因。@Transactional
方法里调第三方接口(微信、短信、外部 API),网络 I/O
可能几秒钟,这几秒里数据库连接一直是占着的。治理方式是”事务边界和工作边界对齐”:
- 先把本地状态落库(比如记为
pending)并提交事务; - 提交后再去同步或异步调外部接口;
- 回调/重试里再用新事务更新最终状态。
循环里逐个 insert
同理,应该改成批量操作(foreach
批量插入)或者分批提交;如果确实数据量大,就分批 + 幂等 +
断点续跑,别指望一个事务吞下十万条。
3. 给事务设超时
别让慢操作无限期拖住连接,@Transactional(timeout = 3)
让超时直接抛出
TransactionTimedOutException,把问题暴露在代码层而不是连接池层。
4. REQUIRES_NEW 与连接池大小的关系
关键在于:REQUIRES_NEW 是”挂起外层 +
开一个全新的独立事务”,它需要从连接池里再拿一条连接,而外层那条连接并不会释放。
1 | // 占用连接 A |
后果是:一次请求就可能占用 2 条甚至更多连接。如果并发
100、每条链路嵌套两层,就要 200 条连接;而
maximum-pool-size 假设只有
20,就会出现”外层全部占用连接、等着内层拿连接,内层又拿不到”的连接池死锁,典型表现是
connection-timeout 超时异常。
所以结论是:REQUIRES_NEW
要当”稀缺资源”用——常见于”审计日志/操作记录必须独立提交”、“失败重试要开新事务不受外层回滚影响”这类场景。用的时候要同步评估连接池大小:线程池并发数
+ 嵌套层数,配合 HikariCP 的 maximumPoolSize 和
connectionTimeout 一起算,并监控活跃连接数。
5. 分清 REQUIRES_NEW 和
NESTED
REQUIRES_NEW |
NESTED |
|
|---|---|---|
| 底层机制 | 挂起外层事务,新建一个独立事务 | 外层事务内的 savepoint(保存点) |
| 连接 | 需要另拿一条连接 | 复用同一条连接 |
| 内层回滚的影响 | 只影响自己,外层不受影响但双方提交时机独立 | 只回滚到 savepoint,外层可以继续提交 |
| 外层回滚的影响 | 内层已提交则无法回滚,数据会残留 | 外层回滚时,内层的修改一起回滚 |
| 是否原子 | 不原子 | 整体仍然是一个事务,可保证原子性 |
一句话选择标准:“内层的失败不能拖累外层,且内层要真的提交”→
REQUIRES_NEW;“内层失败只回滚自己这一小段,整体还要保持一致”→
NESTED。
NESTED 的实现依赖什么?
NESTED 的本质是数据库的 savepoint,而
savepoint 是连接级别的能力,所以要求两个条件同时成立:
- 事务管理器要支持
savepoint:
DataSourceTransactionManager/JdbcTransactionManager是支持的(isNestedTransactionAllowed()为 true); - 底层 JDBC 驱动和数据库要支持:MySQL、PostgreSQL 都支持 savepoint。
所以各个组合的结论是:
| 组合 | NESTED 支持情况 |
|---|---|
MyBatis +
DataSourceTransactionManager |
✅ 支持最直接,就是 connection.setSavepoint()
的原生能力 |
JDBC JdbcTemplate +
JdbcTransactionManager |
✅ 支持 |
JPA/Hibernate +
JpaTransactionManager |
需要确认:它委托给底层的
JdbcTransactionManager,理论上有 savepoint 能力,但要和
Hibernate 的 FlushMode、Session 生命周期配合。坑在于”回滚到
savepoint 之后,Hibernate
一级缓存里可能还留着已回滚的实体状态”,容易出现”数据库回滚了但内存里的对象还以为是脏的”这种不一致。所以我一般不在
JPA 场景用 NESTED,用 REQUIRES_NEW
或者干脆拆事务更稳 |
还有三个容易踩的细节:
没有外层事务时,
NESTED会退化成REQUIRED,也就是自己开一个新事务,此时就没有 savepoint 可言了;内层要正确声明回滚:内层方法上也要写
rollbackFor,否则受检异常一样不回滚,savepoint 也就不会触发;外层必须”消化”内层的异常:因为内层回滚后异常还会向上抛,如果不 catch,外层也会连带整体回滚——那就失去了”内层失败不影响外层”的意义。
实战里我是怎么划事务边界的
- 事务方法必须是 public、必须有明确的
rollbackFor,这是底线规范; - 查询用
readOnly = true,写操作才开事务,别让只读接口占用写事务的资源; - 事务内不调第三方、不发消息、不写缓存,这些统统通过
@TransactionalEventListener(AFTER_COMMIT)挪到提交之后; - 事务粒度尽量小:能在一个方法里几步写完就写完,别跨方法拼装一个巨大的逻辑事务;
REQUIRES_NEW和NESTED按需用,并且评估连接池,别在循环里开新事务;- 跨库/跨服务的一致性不要靠
@Transactional硬扛,用本地消息表、补偿任务或者分布式事务框架。
REQUIRES_NEW
在 JPA 场景有什么额外代价?为什么 NESTED 在 JPA 里要慎用?
@Transactional(readOnly = true)
对 Hibernate 做了什么?能随便加吗?
@Transactional
和持久层的关系:为什么 JPA 的懒加载依赖事务?MyBatis 的 SqlSession
又依赖什么?
同一个事务里
find 两次同一个 ID,是同一个对象吗?同一事务里
save 之后再查同一实体,查到的是新值吗?
Hibernate
为什么
GenerationType.IDENTITY 会让 Hibernate
批量插入彻底失效?你怎么解决?Hibernate 6 里
@GeneratedValue(strategy = AUTO)
的行为变了,你了解吗?
说说实体的四种状态,以及它们之间怎么转换?
什么是持久化上下文(Persistence Context)?EntityManager 是线程安全的吗?
Hibernate 的脏检查(dirty checking)是怎么实现的?
Hibernate 什么时候会把 SQL 发到数据库(flush 的时机)?
延迟加载(懒加载)在
JPA 里是怎么实现的?LazyInitializationException
什么情况下出现?你们项目怎么解决?
为什么
JOIN FETCH 一对多集合再分页会报警告/结果不对? 一次 JOIN
FETCH 两个 List 集合为什么会抛
MultipleBagFetchException?怎么破?
FetchType.LAZY
和 @Fetch(FetchMode.SUBSELECT) 有什么区别?
JPA 的一级缓存和二级缓存分别是什么?二级缓存默认开吗?JPA 的二级缓存和 Redis 缓存的定位区别?你项目里两者怎么分工?
结合
@QueryHints 优化 fetch size
与二级缓存”,@QueryHints 到底能设什么?
hibernate.jdbc.batch_size、order_inserts、order_updates
分别解决什么?
@DynamicUpdate
/ @DynamicInsert 是干什么的?
MyBatis
为什么说 MyBatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
而 MyBatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。
Mybatis里的 # 和 $
的区别?为什么有了 #{} 还要 ${}?
- Mybatis 在处理
#{}时,会创建预编译的 SQL 语句,将 SQL 中的#{}替换为?号,在执行 SQL 时会为预编译 SQL 中的占位符?赋值,调用PreparedStatement的set方法来赋值,预编译的 SQL 语句执行效率高,并且可以防止 SQL 注入,提供更高的安全性,适合传递参数值。 - Mybatis 在处理
${}时,只是创建普通的 SQL 语句,然后在执行 SQL 语句时 MyBatis 将参数直接拼入到 SQL 里,不能防止 SQL 注入,因为参数直接拼接到 SQL 语句中,如果参数未经过验证、过滤,可能会导致安全问题。
XML
Mapper 映射文件中,除了常见的 select、insert、update、delete
标签之外,还有哪些标签?resultType 和
resultMap 怎么选?<id>
标签是干嘛的?
结果映射类:用于处理Java对象与数据库字段之间的复杂映射关系
<resultMap>:自定义结果集映射规则,解决字段名与属性名不一致、或处理复杂的关联对象<id>:映射主键字段。<result>:映射普通字段。<association>:处理一对一的关联关系。<collection>:处理一对多的关联关系。
动态SQL类:在XML中编写灵活的、条件化的SQL语句,避免在Java代码中拼接SQL。
<if>:条件判断,如果test条件为真,则拼接其内部的SQL片段。<choose>, <when>, <otherwise>:实现多条件分支选择<where>:智能处理WHERE子句。它会自动去除第一个条件前多余的AND或OR,并处理是否要插入WHERE关键字。<set>:智能处理UPDATE语句的SET子句。它会动态地添加SET关键字,并自动去除最后一个多余的逗号。foreach>:遍历集合,常用于构建IN条件或批量插入语句。
其他功能类
<selectKey>:用于在插入(<insert>)操作前后,从数据库获取新生成的主键值
MyBatis
能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别?association
和 collection 分别映射什么?一对多 join 出 100 行怎么变成 5
个主对象?
MyBatis 完全支持一对一和一对多的关联查询。它通过
<resultMap> 标签下不同的子标签来实现
嵌套查询
这种方式会执行多条SQL。先查主表,再根据主表的结果,去关联表执行额外的查询。
- 一对一(
<association>):通过单独的 SQL 查询关联对象。 - 一对多(
<collection>):通过单独的 SQL 查询关联的集合。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<!-- 一对一:查询订单及其关联的用户 -->
<resultMap id="OrderWithUserResultMap" type="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<!-- select属性指向另一个namespace下的查询方法,column是传递给它的参数 -->
<association property="user" column="user_id"
select="com.example.mapper.UserMapper.selectUserById"
fetchType="lazy"/>
</resultMap>
<!-- 一对多:查询用户及其所有订单 -->
<resultMap id="UserWithOrdersResultMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<!-- column="user_id" 作为参数传给订单查询 -->
<collection property="orders" column="user_id"
select="com.example.mapper.OrderMapper.selectOrdersByUserId"
fetchType="lazy"/>
</resultMap>select:指定要执行的额外SQL的ID。column:作为参数传递给该SQL的字段。fetchType:可选lazy(懒加载)或eager(立即加载),用于控制性能。
- 一对一(
嵌套结果
只执行一条多表关联查询(JOIN) 的SQL,然后在
<resultMap>中通过映射关系,将结果集填充到对象中。- 一对一(
<association>):使用javaType指定关联对象的类型。 - 一对多(
<collection>):使用ofType指定集合中元素的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<!-- 一对一:一条JOIN查询完成 -->
<resultMap id="OrderWithUserResultMap" type="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<!-- 直接映射关联对象的属性 -->
<association property="user" javaType="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</association>
</resultMap>
<!-- 一对多:一条JOIN查询,注意order表字段的别名 -->
<resultMap id="UserWithOrdersResultMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<!-- ofType指定集合元素类型 -->
<collection property="orders" ofType="Order">
<id property="id" column="order_id"/>
<result property="orderNo" column="order_no"/>
<result property="amount" column="amount"/>
</collection>
</resultMap>- 一对一(
嵌套查询(select 属性)和嵌套结果(join)怎么选?
Mapper
就是个接口没有实现类,为什么能调通?MapperProxy 和
MapperMethod 各干什么? Mapper 方法能重载吗?为什么?
从调用 Mapper 到拿到结果,完整链路讲一遍。
MyBatis 中一级缓存是什么级别?它的 key 由什么组成?什么时候失效?我在 Service 里连续调两次同一个 Mapper 方法,为什么一级缓存没生效?
MyBatis 中二级缓存怎么开?为什么大厂规范普遍禁用它?二级缓存和 Spring 事务一起用会有什么坑?
MyBatis 延迟加载的原理是什么?有哪些失效场景?
PageHelper
的原理是什么?为什么 startPage 之后必须紧跟查询?
MyBatis 中如何处理类型转换?TypeHandler 是干什么的?在哪个环节被调用?
枚举怎么入库?EnumTypeHandler
和 EnumOrdinalTypeHandler 有什么区别?
使用 MyBatis,你们 MySQL 里的 JSON 或者 PG 里的 JSONB 怎么映射?
MyBatis
怎么做多数据源?@DS 或
AbstractRoutingDataSource 的原理是什么?多数据源下 MyBatis
有哪些坑?
MyBatis Plus
Mybatis Plus 和 Mybatis 的区别?能替代吗?
Mybatis Plus是一个基于MyBatis的增强工具库,旨在简化开发并提高效率。
selectById
和 selectOne 有什么区别?selectOne
查到多条会怎样?
Wrapper 有哪几种?讲解一下他们。然后为什么推荐 LambdaWrapper?
MP 的乐观锁插件原理是什么?有哪些限制?
MP 的逻辑删除是怎么实现的?和唯一索引为什么会冲突?
MP
主键默认策略是什么?雪花算法的结构和缺点?MP
的自动填充(MetaObjectHandler)怎么用?不生效的常见原因?
持久层相关实践
你简历里既写 MyBatis Plus 又写 Hibernate,实际项目里怎么选型?给我一个你做决策的标准和思路。
N+1 在 JPA 和 MyBatis 里分别怎么产生、怎么解决?
你说你用预加载解决了
N+1,具体讲讲 N+1 是怎么产生的、你怎么定位的?JPA 里解决 N+1
的所有手段你了解吗?@BatchSize /
default_batch_fetch_size 为什么能把 N+1 变成 1+N
批次?
解释一下你在
AIP 平台写的 PageableAdapter,为什么不能直接把
Page 传给 Dubbo?为什么 Pageable
不适合直接跨服务(比如 Dubbo)传?你写的 PageableAdapter
解决了什么?
你怎么发现并优化一条慢的 JPA 查询?如果用 Arthas 定位过 N+1 和慢 SQL,具体敲了哪些命令、看到了什么?
JPA
怎么做逻辑删除?和 MyBatis-Plus 的 @TableLogic 比呢?
DDD 里的聚合根、值对象怎么落到 JPA 上?有什么阻抗失配?CQRS 的读模型你会用 JPA 还是 MyBatis/原生 SQL?
手写一个插件的步骤是什么?MyBatis 中,你们用插件做过什么?插件顺序有讲究吗?
百万级数据导出/处理,你怎么避免
OOM?Cursor 和 ResultHandler
有什么区别?fetchSize 设了为什么有时不生效?MySQL
有什么特殊要求?流式查询期间有什么风险?
批量插入你有几种写法?性能差多少?你如何进行选择?批量插入怎么回填自增主键?
rewriteBatchedStatements=true不加则 BATCH 模式退化为逐条往返




