Hibernate 从 5 迁移到 5.5 后,将实体添加到集合会导致 ConstraintViolationException
After Hibernate migration from 5 to 5.5 adding entities to collections causes ConstraintViolationException
我们有这样的代码(我简化了代码以使其更清晰):
@Entity
@Table(name="storages")
class Storage {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "storage_items", joinColumns = @JoinColumn(name = "storage_id"), inverseJoinColumns = @JoinColumn(name = "item_id"))
private Set<Item> items;
void putItemToStorage(Session session) {
Item item = new Item();
item.setStorage(this);
session.save(item);
items.add(item);
}
}
@Entity
@Table(name="items")
class Item {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "storage_id")
private Storage storage;
public void setStorage(Storage storage) {
this.storage = storage;
}
}
我们在事务中调用 'putItemToStorage',但在 hibernate 5.5 中它会导致以下错误,而在 hibernate 5 中,相同的代码运行起来非常棒:
> javax.persistence.PersistenceException:
> org.hibernate.exception.ConstraintViolationException: could not execute statement
> ...
> caused by org.postgresql.util.PSQLException: ERROR: insert or update on table
> "storage_items" violates foreign key constraint
> "fkla3c4upmtw4myssb3bfg2svkj" Detail: Key (storage_id)=(164) is not
> present in table "items".
因此,hibernate 5 将两个插入都解析为 items table 和 storage_items table并按预期工作(将 item 添加到 items table 并将 item 链接到通过加入tablestorage_items)对应storage),但在hibernate 5.5中不再有效。我花了很多时间在 google 和文档中,但找不到更改的内容或我做错了什么。
我在其他地方有类似的错误,我暂时解决了它分离保存一个对象并将对象插入到 2 个单独的事务中(它有效,但它绝对不是一个正确的解决方案),所以,任何帮助如何正确修复它,将不胜感激。
您应该只在一侧(保持关系的一侧)定义映射,例如。像这样:
@Entity
@Table(name="storages")
class Storage {
@OneToMany(mappedBy = "storage", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Item> items;
void putItemToStorage() {
Item item = new Item();
item.setStorage(this);
items.add(item);
}
}
@Entity
@Table(name="items")
class Item {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "storage_id", referencedColumnName = "id")
private Storage storage;
public void setStorage(Storage storage) {
this.storage = storage;
}
}
请注意,此方法很可能会导致 2 个查询:
- 这将在数据库中创建
Item
条记录 storage_id = null
- 这将更新记录并将
storage_id
设置为应有的值
为了防止它调整字段上的注释 storage
:
@Entity
@Table(name="items")
class Item {
@ManyToOne(optional = false)
@JoinColumn(name = "storage_id", referencedColumnName = "id", nullable = false, updatable = false)
private Storage storage;
}
您也可以考虑将 orphanRemoval = true
添加到 items
以防您想要删除数据库中的记录。
@Entity
@Table(name="storages")
class Storage {
@OneToMany(mappedBy = "storage", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<Item> items;
}
您的代码应如下所示:
// assumming within transaction context
var storage = // get from eg. EntityManager or JPA repository (in spring)
storage.putItemToStorage();
// There is no need to call EntityManager::persist or EntityManager::merge
// if you are withing transaction context
// and you are working with managed entity
// and have cascade = CascadeType.ALL
我们有这样的代码(我简化了代码以使其更清晰):
@Entity
@Table(name="storages")
class Storage {
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "storage_items", joinColumns = @JoinColumn(name = "storage_id"), inverseJoinColumns = @JoinColumn(name = "item_id"))
private Set<Item> items;
void putItemToStorage(Session session) {
Item item = new Item();
item.setStorage(this);
session.save(item);
items.add(item);
}
}
@Entity
@Table(name="items")
class Item {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "storage_id")
private Storage storage;
public void setStorage(Storage storage) {
this.storage = storage;
}
}
我们在事务中调用 'putItemToStorage',但在 hibernate 5.5 中它会导致以下错误,而在 hibernate 5 中,相同的代码运行起来非常棒:
> javax.persistence.PersistenceException:
> org.hibernate.exception.ConstraintViolationException: could not execute statement
> ...
> caused by org.postgresql.util.PSQLException: ERROR: insert or update on table
> "storage_items" violates foreign key constraint
> "fkla3c4upmtw4myssb3bfg2svkj" Detail: Key (storage_id)=(164) is not
> present in table "items".
因此,hibernate 5 将两个插入都解析为 items table 和 storage_items table并按预期工作(将 item 添加到 items table 并将 item 链接到通过加入tablestorage_items)对应storage),但在hibernate 5.5中不再有效。我花了很多时间在 google 和文档中,但找不到更改的内容或我做错了什么。
我在其他地方有类似的错误,我暂时解决了它分离保存一个对象并将对象插入到 2 个单独的事务中(它有效,但它绝对不是一个正确的解决方案),所以,任何帮助如何正确修复它,将不胜感激。
您应该只在一侧(保持关系的一侧)定义映射,例如。像这样:
@Entity
@Table(name="storages")
class Storage {
@OneToMany(mappedBy = "storage", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Item> items;
void putItemToStorage() {
Item item = new Item();
item.setStorage(this);
items.add(item);
}
}
@Entity
@Table(name="items")
class Item {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "storage_id", referencedColumnName = "id")
private Storage storage;
public void setStorage(Storage storage) {
this.storage = storage;
}
}
请注意,此方法很可能会导致 2 个查询:
- 这将在数据库中创建
Item
条记录storage_id = null
- 这将更新记录并将
storage_id
设置为应有的值
为了防止它调整字段上的注释 storage
:
@Entity
@Table(name="items")
class Item {
@ManyToOne(optional = false)
@JoinColumn(name = "storage_id", referencedColumnName = "id", nullable = false, updatable = false)
private Storage storage;
}
您也可以考虑将 orphanRemoval = true
添加到 items
以防您想要删除数据库中的记录。
@Entity
@Table(name="storages")
class Storage {
@OneToMany(mappedBy = "storage", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<Item> items;
}
您的代码应如下所示:
// assumming within transaction context
var storage = // get from eg. EntityManager or JPA repository (in spring)
storage.putItemToStorage();
// There is no need to call EntityManager::persist or EntityManager::merge
// if you are withing transaction context
// and you are working with managed entity
// and have cascade = CascadeType.ALL