kotlin-backend-jpa-entity-mapping
Model Kotlin persistence code correctly for Spring Data JPA and Hibernate. Covers entity design, identity and equality, uniqueness constraints, relationships, fetch plans, and common ORM (Object-Relational Mapping) traps specific to Kotlin. Use when creating or reviewing JPA (Java Persistence API) e
By kotlin · 1,182 installs
npx skills add kotlin/kotlin-agent-skills --skill kotlin-backend-jpa-entity-mapping
Source repository · Upstream listing
JPA Entity Mapping for Kotlin
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.
Entity Design Rules
Never use data class for JPA entities. Use a regular class . Keep data class for DTOs.
Keep transport DTOs and persistence entities separate unless the project clearly uses a shared model.
Model required columns as non null only when object construction and persistence lifecycle make it safe.
Use lateinit only when the project already accepts that tradeoff and the lifecycle is safe.
Verify kotlin("plugin.jpa") or equivalent no arg support when JPA entities exist.
Verify classes and members are compatible with proxying where needed.
Identity and Equality
Never accept all field equals / hashCode generated by data class on an entity.
Follow project conventions when they already define an identity strategy.
If no convention exists, use ID based equality with a stable hashCode .
For DB generated IDs, model the unsaved state with nullable var id: Long? = null
and a protected set ; do not use 0L as a sentinel value.
Be explicit about mutable fields and lazy associations when discussing equality.
Broken: data class Entity
Correct: Regular Class with ID Based Identity
Key rules:
equals compares by ID only — stable under dirty tracking and proxy unwrapping
hashCode returns class based constant — avoids Set / Map corruption after persist
toString excludes lazy loaded relations — prevents LazyInitializationException
Constructor params are mutable entity fields; DB generated id is nullable with a protected setter
Uniqueness Constraints
When 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.
Broken: No Duplicate Guard
Correct: Database Constraint + Application Guard
Key rules:
Database constraint is mandatory — application checks alone have race conditions
Application check provides clean error messages — without it, users get raw DataIntegrityViolationException
Both layers together: application catches the common case, database catches the race
Spring Data derives findByXAndY queries automatically
Query and Fetch Rules
Diagnose N+1 by looking at actual query count or SQL logs, not by guessing from annotations.
Prefer targeted fetch solutions: @EntityGraph , JOIN FETCH , batch fetching, or DTO projection.
Be careful with collection fetch joins plus pagination — call out the tradeoff.
Use indexes and uniqueness constraints to support real query patterns.
Common ORM Traps
Bidirectional associations: maintain both sides in domain methods. Half updated graphs cause subtle bugs.
orphanRemoval vs cascade remove: not interchangeable. Explain lifecycle semantics before choosing.
Lazy load triggers: toString , debug logging, JSON serialization, and IDE inspection can all trigger lazy loads.
Bulk updates/deletes: bypass persistence context and lifecycle callbacks. Subsequent reads may be stale.
Multiple bag fetches: can cause Cartesian explosion. Verify the ORM can execute collection heavy fetch plans safely.
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.
Guardrails
Do not use data class for JPA entities.
Do not recommend FetchType.EAGER everywhere to silence lazy loading symptoms.
Do not expose entities directly through API responses by default.
Do not claim an N+1 fix without explaining how the fetch plan changes query behavior.