2

我有以下商业模式:

膳食- 包含名称和价格

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

过去我曾使用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) 条目,而不是尝试插入具有相同外键的新条目。

有什么建议可以强制它执行“更新-如果存在-插入-否则”吗?

我通过将每个 HashMap 映射到不同的表来临时解决了这个问题。但我不喜欢这种解决方案。

编辑:我需要我的合并行为如下:

insert into MENU_MEAL (MENU_ID, MEAL_ID, PRICE) values(?, ?, ?) on duplicate key update PRICE=values(price)

任何想法如何实现这一目标?

4

3 回答 3

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 可以获得与以前相同的映射。

于 2014-10-13T07:49:37.327 回答
1

如果我没记错的话,当您使用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()吗?

于 2013-02-14T09:11:01.647 回答
0

当使用 Hibernate 作为 JPA ORM 提供程序时,我找到了解决问题的方法。只需覆盖默认的 SQL INSERT 命令,将@SQLInsertHibernate 注释添加到 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 拥有一个表要好。

于 2013-02-14T10:39:47.733 回答