В нашем домене вы можете отправлять элементы на наш веб-сайт, и каждый элемент может иметь несколько вложений:
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?