IllegalStateException или JpaObjectRetrievalFailureException при сохранении нового элемента в базе данных - PullRequest
0 голосов
/ 01 мая 2020

В нашем домене вы можете отправлять элементы на наш веб-сайт, и каждый элемент может иметь несколько вложений:

Item 1 .. * Attachment

Поскольку мы выполняем очень дорогую операцию над вложениями, и это возможно для дублирования вложений (одно и то же вложение во многих элементах) мы стремимся оптимизировать его, вводя концепцию страхования вложений:

Item 1 .. * AttachementInstance * .. 1 Attachement

(поэтому мы сделаем дорогую часть один раз во вложении и затем, когда мы обнаружим, что приходит то же самое вложение - мы просто ссылаемся на уже обработанное)

  • Item идентифицируется идентификатором, сгенерированным на стороне клиента,
  • AttachmentInstance идентифицируется по пути (уникальный, также известен заранее)
  • Item идентифицируется по ha sh (рассчитывается заранее перед сохранением в дБ)

Здесь определения таблицы для этого:

Элемент

CREATE TABLE item (
    id BIGINT PRIMARY KEY,
    version BIGINT(20)
); 

Вложение

CREATE TABLE attachement (
    hash VARCHAR(64) PRIMARY KEY,
    version BIGINT(20)
);

Экземпляр вложения

CREATE TABLE attachement_instance (
    url VARCHAR(500) PRIMARY KEY,
    item_id BIGINT,
    hash VARCHAR(64),
    version BIGINT(20),
    FOREIGN KEY (hash) REFERENCES attachement(hash),
);

Присоединение к таблице

CREATE TABLE expose_picture_instances (
    item_id BIGINT,
    attachement_instances_url VARCHAR(500),
    FOREIGN KEY (item_id) REFERENCES expose (id) ON DELETE CASCADE,
    FOREIGN KEY (attachement_instances_url) REFERENCES picture_instance (url) ON DELETE CASCADE
);

И ИЛИ начальные классы в Kotlin

Item

@Entity
@Table(name = "item")
data class Item(
  @Id val id: Long = 0,
  @OneToMany(cascade = [CascadeType.ALL]) val attachementInstances: List<AttachementInstance> = emptyList(),
  @Version val version: Long = 0
) {
  // I am overriding hashcode and equals since i've read that the one generated for data classes does not work nice with Hibernate
  override fun hashCode(): Int = return Objects.hashCode(id);

  override fun equals(other: Any?): Boolean {
    other ?: return false
    if (this === other) return true
    if (javaClass != ProxyUtils.getUserClass(other)) return false
    other as Item
    return this.id == other.id
  }
}

AttachementInstance

@Entity
@Table(name = "attachement_instance")
data class AttachementInstance(
  @Id val url: String = "",
  @ManyToOne(cascade = [CascadeType.ALL]) @JoinColumn(name = "hash") val attachement: Attachement? = null,
  @Version val version: Long = 0
) {
    // equals and hash code override the same style as in Item, but looking into "url" field
}

Attachement

@Entity
@Table(name = "attachement")
data class Attachement(
  @Id val hash: String = "",
  @Version val version: Long = 0,
) {
    // equals and hash code override the same style as in Item, but looking into "hash" field
}

И у меня есть пружинное хранилище, определенное следующим образом :

interface ItemRepository : Repository<Item, Long> {
  fun findById(id: Long): Item?
  fun save(expose: Item)
}

Теперь у меня есть сценарий, где у меня все таблицы пусты, и я пытаюсь выполнить следующую операцию:

repo.save(Item(
    id = 1,
    attachementInstances = listOf(
        AttachementInstance(
            url = "path1",
            attachement = Attachement(
                hash = "123" // THE SAME HASH FOR BOTH ATTACHEMENTS
            )
        ),
        AttachementInstance(
            url = "path2",
            attachement = Attachement(
                hash = "123" // THE SAME HASH FOR BOTH ATTACHEMENTS
            )
        ),
    )

Эта операция выдает: Caused by: java.lang.IllegalStateException: Multiple representations of the same entity [de.is24.commercial.imagequality.domain.expose.Instance#13b12312b3] are being merged. Detached: [Instance(hash=13b12312b3, version=0)]; Detached: [Instance(hash=13b12312b3, version=0)]

Я пытался изменить конфигурацию каскада в экземпляре вложения с CascadeType.ALL на CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH, CascadeType.DETACH (чтобы удалить тип каскада слияния), но я получаю следующее исключение: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find de.is24.commercial.imagequality.domain.expose.Instance with id 13b12312b3; nested exception is javax.persistence.EntityNotFoundException: Unable to find de.is24.commercial.imagequality.domain.expose.Instance with id 13b12312b3

Что должно быть правильным способ моделирования такого домена с помощью JPA / Hibernate и Spring Data в Kotlin?

...