К основному содержанию
скилл CX Codex

kotlin-backend-jpa-entity-mapping — скилл для Claude Code

Учит агента правильно писать JPA-сущности на Kotlin для Spring Data JPA и Hibernate: идентичность, equals/hashCode, связи, выборки.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add Kotlin/kotlin-agent-skills --skill kotlin-backend-jpa-entity-mapping -a claude-code
Открыть на GitHub

Что делает kotlin-backend-jpa-entity-mapping

Скилл от JetBrains разбирает ловушки, в которые попадают при маппинге сущностей на Kotlin. Главная из них — data class: Hibernate опирается на идентичность, а сгенерированные equals/hashCode по всем полям ломают членство в Set и Map после изменения состояния. Автогенерируемый copy() вдобавок создаёт отсоединённые копии управляемых сущностей. Скилл требует обычный class для сущностей, а data class оставляет для DTO.

Внутри — правила проектирования сущностей, стратегии идентичности и ограничения уникальности. Для ID из базы предлагается var id: Long? = null с protected set вместо сентинела 0L. equals сравнивает только по ID, hashCode возвращает константу класса, toString не трогает ленивые коллекции. Уникальность советует держать на двух уровнях: ограничение в базе для корректности и проверка в приложении ради понятных ошибок — одна проверка в коде гонки не закрывает.

Есть раздел про N+1 и LazyInitializationException: диагностировать по реальному числу запросов или SQL-логам, а не по аннотациям. Из решений — @EntityGraph, JOIN FETCH, batch fetching, проекции в DTO. Отдельно про ловушки ORM: orphanRemoval и cascade remove не взаимозаменяемы, массовые update/delete обходят persistence context, несколько fetch-джойнов коллекций дают декартово произведение. Если в проекте уже принята своя стратегия идентичности, скилл советует следовать ей, а не навязывать свою.

Какие задачи решает

  • переписать JPA-сущность с data class на обычный класс
  • починить equals/hashCode, чтобы Set не терял элементы после persist
  • разобраться, откуда берётся N+1, по SQL-логам
  • добавить ограничение уникальности и понятную ошибку на дубликат
  • вынести ленивые связи из toString, чтобы не ловить LazyInitializationException

Примеры запросов агенту

  • посмотри мою сущность Order и скажи, что в ней сломается под Hibernate
  • почему у меня N+1 при выборке заказов с позициями? покажи, как переписать запрос
  • добавь уникальность на пару variant_id и order_id в Reservation и проверку в сервисе

Как установить kotlin-backend-jpa-entity-mapping

1Через Skills CLI (Vercel)

npx skills add Kotlin/kotlin-agent-skills --skill kotlin-backend-jpa-entity-mapping -a claude-code

2Вручную: положить файлы

~/.claude/skills/kotlin-backend-jpa-entity-mapping/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/kotlin-backend-jpa-entity-mapping/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о kotlin-backend-jpa-entity-mapping

Как установить kotlin-backend-jpa-entity-mapping в Claude?

Выполните npx skills add Kotlin/kotlin-agent-skills --skill kotlin-backend-jpa-entity-mapping -a claude-code. Файл скилла окажется по пути ~/.claude/skills/kotlin-backend-jpa-entity-mapping/SKILL.md.

Что делает этот скилл?

Даёт агенту правила маппинга JPA-сущностей на Kotlin для Spring Data JPA и Hibernate: проектирование сущностей, идентичность и равенство, ограничения уникальности, связи и планы выборки.

Почему data class нельзя использовать для JPA-сущностей?

Сгенерированные equals/hashCode считаются по всем полям, поэтому после изменения состояния сущность выпадает из Set или Map. Плюс copy() создаёт отсоединённую копию управляемой сущности.

Исходный текст

SKILL.md · Apache-2.0
---
name: kotlin-backend-jpa-entity-mapping
description: >
  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) 
  entities, diagnosing N+1 or LazyInitializationException, placing indexes and 
  uniqueness rules, or preventing Kotlin-specific bugs such as data class 
  entities and broken equals/hashCode.
license: Apache-2.0
metadata:
  author: JetBrains
  version: "1.0.0"
---

# 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

```kotlin
// WRONG: data class generates equals/hashCode from ALL fields,
// and the generated ID uses a 0 sentinel instead of null
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)
```

### Correct: Regular Class with ID-Based Identity

```kotlin
@Entity
@Table(name = "orders")
class Order(
    @Column(nullable = false)
    var status: String,

    @Column(nullable = false)
    var total: BigDecimal
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null
        protected set

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Order) return false
        return id != null && 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 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

```kotlin
@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))
    }
}
```

### Correct: Database Constraint + Application Guard

```kotlin
@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)
    var id: Long? = null
        protected set
}

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:**
- 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.

© JetBrains · Apache-2.0 · Kotlin/kotlin-agent-skills

Похожие скиллы

Всё по теме «Бэкенд и API» →
jpa-patterns affaan-m Скилл подсказывает правила моделирования JPA-сущностей, устранения проблемы N+1 и настройки пула HikariCP в Spring Boot. Пригодится при написании репозиториев и оптимизации запросов. CX Codex +1 4 600установок 412-frameworks-quarkus-panache Juan Antonio Breña Moral Скилл для работы с Hibernate ORM Panache в Quarkus: подсказывает паттерны запросов, проекций и блокировок, проверяя сборку до и после правок. CX Codex +1 331установок spring-boot-development manutej Скилл для ИИ-агента, который подсказывает, как писать Spring Boot: авто-конфигурация, внедрение зависимостей, REST-контроллеры, Spring Data и Spring Security. CX Codex +1 534установок spring-data-jpa giuseppe-trisciuoglio Шаблоны и правила для слоя персистентности на Spring Data JPA: создание репозиториев, пагинация, аудит сущностей и настройка транзакций. CX Codex +1 4 100установок convert-jpa-to-spring-data-cosmos официальный github Пошаговое правило для Copilot: переводит Spring Boot проект с JPA на Azure Cosmos DB через Spring Data Cosmos. Пригодится, когда миграция уже решена и нужен план. ★ 40 418звёзд репо kotlin-springboot официальный github Скилл задаёт практики Spring Boot и Kotlin: от структуры проекта до тестов и корутин. Подходит, когда агент пишет или проверяет серверное приложение. 9 781установок

Из того же набора10

kotlin-tooling-agp9-migration Ведёт проект Kotlin Multiplatform через переход на Android Gradle Plugin 9.0: замена плагинов, разбиение модулей, перенос DSL. CX Codex +1 2 200установок kotlin-tooling-java-to-kotlin Конвертирует Java-файлы в идиоматичный Kotlin по четырёхшаговой методике с проверкой инвариантов и подсказками под популярные фреймворки. CX Codex +1 2 000установок kotlin-tooling-cocoapods-spm-migration Скилл переносит KMP-проект с CocoaPods на Swift Package Manager: меняет Gradle DSL, импорты Kotlin и настройки Xcode. Нужен для поэтапной миграции. CX Codex +1 1 800установок kotlin-tooling-immutable-collections-0-5-x-migration Скилл ведёт миграцию Kotlin и Java с kotlinx.collections.immutable 0.3.x и 0.4.x на 0.5.x: поднимает версию и переименовывает методы по подсказкам компилятора. CX Codex +1 1 000установок kotlin-tooling-native-build-performance Разбирает, почему iOS-сборка в Kotlin Multiplatform идёт медленно, и предлагает безопасные правки с замером до и после. CX Codex +1 943установок kotlin-tooling-kotlin-toolchain Скилл учит агента работать с Kotlin Toolchain — единым CLI JetBrains вместо Gradle-скриптов. Пригодится, если в репозитории лежат project.yaml и module.yaml. CX Codex +1 449установок kotlin-tooling-gradle-to-kotlin-toolchain-project Пошагово переводит проект с Gradle на Kotlin Toolchain: переносит зависимости, заменяет плагины и правит CI. Пригодится при полной миграции репозитория без изменения бизнес-логики. CX Codex +1 426установок kotlin-tooling-kotlin-toolchain-plugin-authoring Помогает писать локальные плагины Kotlin Toolchain: генерация кода, проверки и упаковка, которые не выразить в module.yaml. CX Codex +1 419установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой