2013-02-14 101 views
2
相同的實體按鍵

我有以下的商業模式:映射多個包含HashMap在JPA

膳食 - 包含名稱和價格

菜單 - 包含名稱和它們的量的套餐和替代價格(通常低於默認價格)。

在過去,我已經使用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。

我添加新的餐的菜單是這樣的(在我的實體內的方法),我做到這一點,而它的分離(不想存儲餐DB馬上 - 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

當使用Hibernate作爲JPA ORM提供程序時,我找到了解決我的問題的方法。只需重寫默認的SQL INSERT命令,將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有一個表更好。

+1

我很高興它現在可以工作,但我同意這不是最好的解決方案。在7個小時內(是的,漫長的一天),我可以爲你研究這個。也許我可以爲你找到更乾淨的東西。 – Joetjah 2013-02-14 10:57:28

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()嗎?

+0

我首先創建一個沒有任何膳食的菜單並將其存儲。之後,我將它分開,因此我可以添加Meals而不將其存儲在數據庫中(僅用於在GUI中進行預覽)。點擊確定後,em.merge(menu)被調用,在這裏我希望所有添加的Meals都被保留。過去只有一個HashMap運行良好。 – 2013-02-14 09:26:02

+0

你可以試試你的代碼,但是然後'em.merge'而不是'em.persist'? – Joetjah 2013-02-14 09:28:43

+0

我的錯誤,我確實是'em.merge()',而不是'em.persist()'。 – 2013-02-14 09:30:50

3

我知道我遲到了,但這些都是我的解決方案:

1)使用單一的地圖與@Embeddable值:

@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關係:

@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玩,你可以得到相同的映射爲以前。