我有以下商业模式:
用餐 - 包含名称和价格
菜单 - 包含名称和一组膳食及其金额和替代价格(通常低于默认价格)。
过去我使用HashMap<Entity, Value>
解决了类似的问题。在这种情况下,我有两个值:价格和金额。由于Java和JPA不支持multimaps,我决定使用两个包含相同键的HashMaps。
@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。
我将新的Meals添加到菜单中(在我的实体内部的方法中),并且在分离时执行此操作(不想立即将Meals存储到数据库中 - 首先是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)
任何想法如何实现?
答案 0 :(得分:3)
我知道我迟到了,但这些是我的解决方案:
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可以获得与之前相同的映射。
答案 1 :(得分:1)
如果我没记错,当您使用variable = em.find
并更新该变量时,您可以更新entitymanager中的值而不会持久化。
您可以通过以下方式包围您更改或添加值的代码:
em.getTransaction().begin();
//change
menu.prices.add(meal, price);
menu.amounts.add(meal, amount);
em.getTransaction().commit();
您也可以尝试em.flush()
。
如果您显示代码的创建方式menu
,则可以给出更确定的答案。您使用的是em.find()
吗?
答案 2 :(得分:0)
当使用Hibernate作为JPA ORM提供程序时,我找到了解决我的问题的方法。只需覆盖默认的SQL INSERT命令,即可向HashMaps添加@SQLInsert
Hibernate注释。
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配备一个表更好。