在 JPA 中使用相同的实体键映射多个 HashMap
Posted
技术标签:
【中文标题】在 JPA 中使用相同的实体键映射多个 HashMap【英文标题】:Mapping multiple HashMaps with same Entity keys in JPA 【发布时间】:2013-02-14 09:07:11 【问题描述】:我有以下商业模式:
膳食 - 包含名称和价格
菜单 - 包含名称和一组膳食及其数量和替代价格(通常低于默认价格)。
过去我曾使用HashMap<Entity, Value>
解决过类似的问题。在这种情况下,我有两个值:价格和数量。由于 Java 和 JPA 不支持多映射,我决定使用两个包含相同键的 HashMap。
@Entity
public class Menu
@Column
private String name;
@ElementCollection
@CollectionTable(name = "MENU_MEALS", joinColumns = @JoinColumn(name = "MENU_ID"))
@MapKeyJoinColumn(name = "MEAL_ID", referencedColumnName = "ID")
@Column(name="AMOUNT")
private Map<Meal, BigDecimal> amounts = new LinkedHashMap<Meal, BigDecimal>();
@ElementCollection
@CollectionTable(name = "MENU_MEALS", joinColumns = @JoinColumn(name = "MENU_ID"))
@MapKeyJoinColumn(name = "MEAL_ID", referencedColumnName = "ID")
@Column(name="PRICE")
private Map<Meal, BigDecimal> prices = new LinkedHashMap<Meal, BigDecimal>();
生成的表看起来不错:MENU_ID、MEAL_ID、AMOUNT、PRICE。
我像这样(在我的实体内部的一个方法中)向菜单添加新餐点,并且在它分离时执行此操作(不想立即将餐点存储到数据库 - 首先是 GUI 预览):
menu.prices.add(meal, price);
menu.amounts.add(meal, amount);
在分离的菜单中添加足够的餐食并单击“确定”按钮后,我的菜单被合并。
em.merge(menu)
操作映射为以下 SQL:
insert into MENU_MEAL (MENU_ID, MEAL_ID, PRICE) values (?, ?, ?)
insert into MENU_MEAL (MENU_ID, MEAL_ID, AMOUNT) values (?, ?, ?)
这就是问题所在。我得到一个主键约束违反异常。第二次调用应该更新 (MENU_ID, MEAL_ID) 条目,而不是尝试插入具有相同外键的新条目。
有什么建议可以强制它执行“update-if-exists-insert-otherwise”吗?
我通过将每个 HashMap 映射到不同的表来临时解决了这个问题。但我不喜欢这种解决方案。
编辑:我需要我的合并行为如下:
insert into MENU_MEAL (MENU_ID, MEAL_ID, PRICE) values(?, ?, ?) on duplicate key update PRICE=values(price)
有什么想法可以实现吗?
【问题讨论】:
【参考方案1】:我知道我迟到了,但这些是我的解决方案:
1) 使用具有 @Embeddable 值的单个 Map:
@Entity
public class Menu
@Column
private String name;
@ElementCollection
@CollectionTable(name = "MENU_MEALS", joinColumns = @JoinColumn(name = "MENU_ID"))
@MapKeyJoinColumn(name = "MEAL_ID", referencedColumnName = "ID")
private Map<Meal, Detail> details = new LinkedHashMap<Meal, Detail>();
@Embeddable
public class Detail
@Column(name = "AMOUNT")
private BigDecimal amount;
@Column(name = "PRICE")
private BigDecimal price;
此映射与您已有的表结构相同。
2) 使用具有@Entity 关系的单个 Map:
@Entity
public class Menu
@Column
private String name;
@OneToMany(mappedBy = "menu", cascade = CascadeType.ALL, orphanRemoval = true)
@MapKey(name = "meal")
private Map<Meal, MealDetail> details = new LinkedHashMap<Meal, MealDetail>();
@Entity
public class MealDetail
@ManyToOne
@JoinColumn(name = "MENU_ID")
private Menu menu;
@ManyToOne
@JoinColumn(name = "MEAL_ID")
private Meal meal;
@Column(name = "AMOUNT")
private BigDecimal amount;
@Column(name = "PRICE")
private BigDecimal price;
此映射添加了一个 ID 列,但我认为使用 @PrimaryKeyJoinColumns 可以获得与以前相同的映射。
【讨论】:
【参考方案2】:如果我没记错的话,当您使用variable = em.find
并更新该变量时,您可以更新实体管理器中的值而无需持久化。
您可以将更改的代码包围起来,也可以通过以下方式添加值:
em.getTransaction().begin();
//change
menu.prices.add(meal, price);
menu.amounts.add(meal, amount);
em.getTransaction().commit();
你也可以试试em.flush()
。
如果您显示代码是如何创建menu
,则可以给出更确定的答案。你用过em.find()
吗?
【讨论】:
我首先创建一个没有任何餐点的菜单并将其存储。之后,我将其分离,因此我可以添加膳食而不将其存储在数据库中(仅用于在 GUI 中预览)。单击“确定”后,将调用 em.merge(menu) ,在这里我希望所有添加的 Meals 也会被保留。过去它只在一个 HashMap 上运行良好。 你可以试试你的代码,但是然后em.merge
而不是em.persist
?
我的错,我实际上是em.merge()
,而不是em.persist()
。
由于我不在家,我恐怕无法在此进行进一步测试,但我知道您正在尝试加入表两次。我认为您尝试将菜单与膳食和价格一起加入,从而为膳食创建双键?不过只是猜测。
我设法通过使用 @SQLInsert 注释覆盖 SQL INSERT 命令来修复它。看看我下面的答案。【参考方案3】:
当使用 Hibernate 作为 JPA ORM 提供程序时,我找到了解决问题的方法。只需覆盖默认的 SQL INSERT 命令,将 @SQLInsert
Hibernate 注释添加到 HashMap。
H2数据库解决方案:
@SQLInsert(sql="MERGE INTO MENU_MEAL(MENU_ID, MEAL_ID, PRICE) VALUES (?, ?, ?)")
private Map<Meal, BigDecimal> prices = new LinkedHashMap<Meal, BigDecimal>();
@SQLInsert(sql="MERGE INTO MENU_MEAL(MENU_ID, MEAL_ID, AMOUNT) VALUES (?, ?, ?)")
private Map<Meal, BigDecimal> amounts = new LinkedHashMap<Meal, BigDecimal>();
其他数据库的解决方案(支持 ON DUPLICATE KEY):
@SQLInsert(sql="INSERT INTO MENU_MEAL(MENU_ID, MEAL_ID, PRICE) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE set PRICE = VALUES(PRICE)")
private Map<Meal, BigDecimal> prices = new LinkedHashMap<Meal, BigDecimal>();
@SQLInsert(sql="INSERT INTO MENU_MEAL(MENU_ID, MEAL_ID, AMOUNT) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE set AMOUNT = VALUES(AMOUNT)")
private Map<Meal, BigDecimal> amounts = new LinkedHashMap<Meal, BigDecimal>();
对解决方案仍然不完全满意(依赖于数据库和 ORM 提供程序),但比为每个 HashMap 拥有一个表要好。
【讨论】:
我很高兴它现在可以工作,但我同意这不是最好的解决方案。在 7 小时内(是的,漫长的一天),我将能够为您调查此问题。也许我可以为你找到更干净的东西。以上是关于在 JPA 中使用相同的实体键映射多个 HashMap的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot JPA:将一个实体映射到具有相同列的多个(很多)表