移动/游戏
Kotlin + Spring Data JPA…
在编写或审查 Kotlin 的 JPA/Hibernate 实体、诊断 N+1 或 LazyInitializationException、设计索引与唯一约束时使用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
Kotlin + Spring Data通用
解决思路
- 永远不要用 `data class` 定义 JPA 实体,改用普通 `class`;`data class` 只用于 DTO。
- 除非项目明确使用共享模型,否则保持传输 DTO 与持久化实体分离。
- 只有当对象构造与持久化生命周期都安全时,才把必填列建模为非空。
- 只有在项目已接受该权衡且生命周期安全时,才使用 `lateinit`。
- 当存在 JPA 实体时,确认已启用 `kotlin("plugin.jpa")` 或等价的 no-arg 支持。
- 确认类与成员在需要时兼容代理(proxying)。
- 绝不接受 `data class` 为实体生成的全字段 `equals`/`hashCode`。
- 若项目已有身份策略约定,遵循项目约定。
- 若无约定,使用基于 ID 的相等性并配稳定的 `hashCode`。
- 对于数据库生成的 ID,用可空 `var id: Long? = null` 加 `protected set` 建模未保存状态;不要用 `0L` 作哨兵值。
- 讨论相等性时,明确说明可变字段与懒加载关联。
- `equals` 只按 ID 比较——在脏检查与代理解包下保持稳定。
- `hashCode` 返回基于类的常量——避免 persist 后 `Set`/`Map` 成员关系被破坏。
- `toString` 排除懒加载关系——防止 `LazyInitializationException`。
- 构造参数为可变实体字段;数据库生成的 `id` 可空且带 protected setter。
- 当 API 必须幂等时(例如“为订单 X 预留库存”),在两层强制唯一性:数据库约束保证正确性,应用层检查提供清晰错误。
- 数据库约束是强制的——仅靠应用层检查存在竞态条件。
- 应用层检查提供清晰错误信息——否则用户会看到原始 `DataIntegrityViolationException`。
- 两层结合:应用层捕获常见情况,数据库捕获竞态。
- Spring Data 会自动派生 `findByXAndY` 查询。
- 通过实际查询计数或 SQL 日志诊断 N+1,而不是凭注解猜测。
- 优先使用有针对性的抓取方案:`@EntityGraph`、`JOIN FETCH`、批量抓取或 DTO 投影。
- 注意集合 fetch join 加分页的组合——要指出其权衡。
- 使用索引与唯一约束来支撑真实查询模式。
- 双向关联:在领域方法中维护两侧,半更新的对象图会导致隐蔽 bug。
- `orphanRemoval` 与 cascade remove 不可互换;选择前先解释生命周期语义。
- 懒加载触发点:`toString`、调试日志、JSON 序列化、IDE 检查都可能触发懒加载。
- 批量更新/删除会绕过持久化上下文与生命周期回调,后续读取可能拿到陈旧数据。
- 多个 bag 抓取可能导致笛卡尔积爆炸;确认 ORM 能安全执行集合密集的抓取计划。
- `Set` + 可变相等性:实体状态变化后集合成员关系可能被破坏。
- 当并发更新重要时,`@Version` 是最清晰的乐观并发机制。
- 禁用 `open-in-view` 时,触碰懒加载字段的 DTO 映射必须在事务边界内完成。
- 不要用 `data class` 定义 JPA 实体。
- 不要为了掩盖懒加载症状而到处推荐 `FetchType.EAGER`。
- 默认不要通过 API 响应直接暴露实体。
- 不要在未解释抓取计划如何改变查询行为的情况下声称修复了 N+1。
适用边界 · 注意事项
- 项目使用非 JPA 的持久化方案(如 Exposed、jOOQ、MyBatis)时不适用。
- 项目未使用 Kotlin 或未使用 Spring Data JPA/Hibernate 时不适用。
- 仅做纯 DTO 建模、不涉及持久化实体时不适用。