在JPA中使用相同的实体键映射多个HashMaps

时间:2013-02-14 09:07:12

标签: java hibernate jpa orm h2

我有以下商业模式:

用餐 - 包含名称和价格

菜单 - 包含名称和一组膳食及其金额和替代价格(通常低于默认价格)。

过去我使用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)

任何想法如何实现?

3 个答案:

答案 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配备一个表更好。