重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
kotlin-backend-jpa-entity-mapping by kotlin/kotlin-agent-skills
npx skills add https://github.com/kotlin/kotlin-agent-skills --skill kotlin-backend-jpa-entity-mappingKotlin 的 data class 天然适合 DTO,但对 JPA 实体来说是危险的。Hibernate 依赖于 data class 所破坏的标识语义:基于所有字段的 equals/hashCode 会在状态改变后破坏 Set/Map 的成员关系,而自动生成的 copy() 会创建托管实体的分离副本。
本技能教授 Kotlin + Spring Data JPA 项目中正确的实体设计、标识策略和唯一性约束。
data class。 使用普通的 class。将 保留给 DTO 使用。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
data classlateinit。kotlin("plugin.jpa") 或等效的无参支持。data class 生成的全字段 equals/hashCode。hashCode。data class 实体// 错误:data class 从所有字段生成 equals/hashCode
data class Order(
@Id @GeneratedValue val id: Long = 0,
var status: String,
var total: BigDecimal
)
// 缺陷:order.status = "SHIPPED"; set.contains(order) → false (哈希值已改变)
// 缺陷:Hibernate proxy.equals(entity) → false (代理的延迟字段未初始化)
@Entity
@Table(name = "orders")
class Order(
@Column(nullable = false)
var status: String,
@Column(nullable = false)
var total: BigDecimal
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Order) return false
return id != 0L && id == other.id
}
override fun hashCode(): Int = javaClass.hashCode()
// toString 绝不能引用延迟加载的集合
override fun toString(): String = "Order(id=$id, status=$status)"
}
关键规则:
equals 仅通过 ID 比较 —— 在脏数据跟踪和代理解包下保持稳定hashCode 返回基于类的常量 —— 避免持久化后 Set/Map 损坏toString 排除延迟加载的关系 —— 防止 LazyInitializationExceptionid 是带有默认值的 val当 API 必须是幂等的(例如,“为订单 X 预留库存”)时,需要在两个层面强制执行唯一性:数据库约束用于保证正确性,应用检查用于提供清晰的错误信息。
@Service
class ReservationService(private val repo: ReservationRepository) {
@Transactional
fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {
// 缺陷:没有检查 —— 重复项会静默累积
return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))
}
}
@Entity
@Table(
name = "reservations",
uniqueConstraints = [
UniqueConstraint(columnNames = ["variant_id", "order_id"])
]
)
class Reservation(
@Column(name = "variant_id", nullable = false)
val variantId: Long,
@Column(name = "order_id", nullable = false)
val orderId: String,
@Column(nullable = false)
var quantity: Int
) {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
}
interface ReservationRepository : JpaRepository<Reservation, Long> {
fun findByVariantIdAndOrderId(variantId: Long, orderId: String): Reservation?
}
@Service
class ReservationService(private val repo: ReservationRepository) {
@Transactional
fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {
repo.findByVariantIdAndOrderId(variantId, orderId)?.let {
throw IllegalStateException(
"Reservation already exists for variant=$variantId, order=$orderId"
)
}
return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))
}
}
关键规则:
DataIntegrityViolationExceptionfindByXAndY 查询@EntityGraph、JOIN FETCH、批量获取或 DTO 投影。orphanRemoval 与级联删除: 不可互换。在选择前解释生命周期语义。toString、调试日志、JSON 序列化和 IDE 检查都可能触发延迟加载。Set + 可变相等性: 实体状态改变后,集合成员关系可能被破坏。@Version: 当并发更新重要时,这是最清晰的乐观并发机制。open-in-view: 触及延迟字段的 DTO 映射必须在事务边界内完成。data class。FetchType.EAGER。每周安装量
52
代码仓库
GitHub 星标数
224
首次出现
9 天前
安全审计
安装于
gemini-cli48
github-copilot48
codex48
opencode48
kimi-cli47
amp47
Kotlin's data class is natural for DTOs but dangerous for JPA entities. Hibernate relies on identity semantics that data class breaks: equals/hashCode over all fields corrupts Set/Map membership after state changes, and auto-generated copy() creates detached duplicates of managed entities.
This skill teaches correct entity design, identity strategies, and uniqueness constraints for Kotlin + Spring Data JPA projects.
data class for JPA entities. Use a regular class. Keep data class for DTOs.lateinit only when the project already accepts that tradeoff and the lifecycle is safe.kotlin("plugin.jpa") or equivalent no-arg support when JPA entities exist.equals/hashCode generated by data class on an entity.hashCode.data class Entity// WRONG: data class generates equals/hashCode from ALL fields
data class Order(
@Id @GeneratedValue val id: Long = 0,
var status: String,
var total: BigDecimal
)
// BUG: order.status = "SHIPPED"; set.contains(order) → false (hash changed)
// BUG: Hibernate proxy.equals(entity) → false (proxy has lazy fields uninitialized)
@Entity
@Table(name = "orders")
class Order(
@Column(nullable = false)
var status: String,
@Column(nullable = false)
var total: BigDecimal
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Order) return false
return id != 0L && id == other.id
}
override fun hashCode(): Int = javaClass.hashCode()
// toString must NOT reference lazy collections
override fun toString(): String = "Order(id=$id, status=$status)"
}
Key rules:
equals compares by ID only — stable under dirty tracking and proxy unwrappinghashCode returns class-based constant — avoids Set/Map corruption after persisttoString excludes lazy-loaded relations — prevents LazyInitializationExceptionid is val with defaultWhen an API must be idempotent (e.g., "reserve stock for order X"), enforce uniqueness at both layers: database constraint for correctness, application check for clean errors.
@Service
class ReservationService(private val repo: ReservationRepository) {
@Transactional
fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {
// BUG: no check — duplicates silently accumulate
return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))
}
}
@Entity
@Table(
name = "reservations",
uniqueConstraints = [
UniqueConstraint(columnNames = ["variant_id", "order_id"])
]
)
class Reservation(
@Column(name = "variant_id", nullable = false)
val variantId: Long,
@Column(name = "order_id", nullable = false)
val orderId: String,
@Column(nullable = false)
var quantity: Int
) {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0
}
interface ReservationRepository : JpaRepository<Reservation, Long> {
fun findByVariantIdAndOrderId(variantId: Long, orderId: String): Reservation?
}
@Service
class ReservationService(private val repo: ReservationRepository) {
@Transactional
fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {
repo.findByVariantIdAndOrderId(variantId, orderId)?.let {
throw IllegalStateException(
"Reservation already exists for variant=$variantId, order=$orderId"
)
}
return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))
}
}
Key rules:
DataIntegrityViolationExceptionfindByXAndY queries automatically@EntityGraph, JOIN FETCH, batch fetching, or DTO projection.orphanRemoval vs cascade remove: not interchangeable. Explain lifecycle semantics before choosing.toString, debug logging, JSON serialization, and IDE inspection can all trigger lazy loads.Set + mutable equality: collection membership can break after entity state changes.@Version: the clearest optimistic concurrency mechanism when concurrent updates matter.open-in-view disabled: DTO mapping touching lazy fields must happen inside a transaction boundary.data class for JPA entities.FetchType.EAGER everywhere to silence lazy loading symptoms.Weekly Installs
52
Repository
GitHub Stars
224
First Seen
9 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli48
github-copilot48
codex48
opencode48
kimi-cli47
amp47
Kotlin Exposed ORM 模式指南:DSL查询、DAO、事务管理与生产配置
1,300 周安装
GitHub代码安全审计工具 - 自动化查找缺陷、安全漏洞与代码质量问题
1,000 周安装
Python Excel自动化:openpyxl库操作XLSX文件教程,创建编辑格式化电子表格
961 周安装
数据分析技能:使用DuckDB高效分析Excel/CSV文件,支持SQL查询与统计摘要
966 周安装
Sensei:GitHub Copilot for Azure技能合规性自动化改进工具
965 周安装
video-frames:使用ffmpeg从视频中提取单帧和缩略图的命令行工具
998 周安装
Flutter无障碍访问与自适应设计指南:实现WCAG标准与响应式布局
975 周安装