2012-10-15 40 views
0

質詢結果:JPA合併在外國實體的重複條目

有誰知道如何無需EntityManager試圖重新插入外國實體合併嗎?

場景:

剛成立,我的情況下緊密匹配的情形:我有兩個實體

@Entity 
@Table(name = "login", catalog = "friends", uniqueConstraints = 
@UniqueConstraint(columnNames = "username")) 
public class Login implements java.io.Serializable{ 

    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = IDENTITY) 
    @Column(name = "id", unique = true, nullable = false) 
    private Integer id; 
    @Column(name = "username", unique = true, nullable = false, length = 50) 
    private String username; 
    @Column(name = "password", nullable = false, length = 250) 
    private String password; 
} 

@Entity 
@Table(name = "friendshiptype", catalog = "friends") 
public class FriendshipType implements java.io.Serializable{ 

    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = IDENTITY) 
    @Column(name = "id", unique = true, nullable = false) 
    private Integer id; 
    @OneToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "username") 
    private Login login; 
     @Column(name = "type", unique = true, length = 32) 
    private String type; 
    ...//other fields go here 
} 

無論是Login實體和FriendshipType實體單獨保存到數據庫。然後,稍後,我需要合併Login行和FriendshipType行。當我打電話entityManager.merge(friendship),它試圖插入一個新的Login這當然會導致以下錯誤

Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'myUserName1350319637687' for key 'username' 
Error Code: 1062 
Call: INSERT INTO friends.login (password, username) VALUES (?, ?) 

我的問題,又是我怎麼合併兩個對象,而無需enityManager試圖重新插入異物?

+2

這可能與您的問題無關,但不應該是@JoinColumn(name =「id」)。我見過的所有例子都通過id屬性加入。 –

+0

@Guido Simone我嘗試了你的建議,但沒有改變。 – kasavbere

+1

好的 - 謝謝你讓我知道。希望你得到一些更好的答案。 –

回答

5

這是我如何解決問題。我終於明白合併未解決的原因是因爲login.id是由JPA自動生成的。所以,因爲我真的不需要一個自動生成的ID字段,我從架構中取出,並用username作爲@id領域:

@Entity 
@Table(name = "login", catalog = "friends", uniqueConstraints = 
@UniqueConstraint(columnNames = "username")) 
public class Login implements java.io.Serializable{ 

    private static final long serialVersionUID = 1L; 
    @Id 
    @Column(name = "username", unique = true, nullable = false, length = 50) 
    private String username; 
    @Column(name = "password", nullable = false, length = 250) 
    private String password; 
} 

發生我一個解決方案,我沒有實現,但可以幫助其他人,如果他們需要有一個自動生成的ID字段。

不是爲合併創建Login的實例,而是從數據庫中獲取實例。我的意思是不是

Login login = new Login(); login.setUsername(username); login.setPassword(password); 

做,而

Login login = loginDao.getByUsername(username); 

這樣的話,也不會產生新的id字段使得實體似乎不同。

非常感謝大家的幫助,尤其是感謝@mijer的耐心。

+0

我很高興你找到了一個解決方案kasavbere,但從長遠來看,我不認爲刪除自動生成的id來建立關係是一個很好的設計權衡。花點時間讓使用'@ ManyToOne'關係的想法沉入你腦海中(那些阻抗不匹配問題比你想象的更爲常見,下次碰到其中一個時可能不會解決PK問題)。 –

+0

我正在使用'用戶名'作爲主鍵。正如我在替代解決方案中提到的,如果我需要它,我可以很好地保留自動生成的字段。我認爲'@ ManyToOne'可能是誤導性的,因爲這個關係實際上是'@ OneToOne'。儘管如此,任何一個人都沒有解決問題,我仍然在使用'@ ManyToOne'解決方案 - 我還沒有改變它。 – kasavbere

+0

奇怪的是,'@ ManyToOne'註解應該可以解決這個問題,除非你用'FriendshipType'(你的第二個建議可以解決這個問題)持久化沒有附加的'Login'對象。 –

1

你試過cascade=MERGE?即

@OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE) 
@JoinColumn(name = "username") 
private Login login; 

UPDATE

另一個可能的選擇是使用@ManyToOne(它另存爲協會是唯一的)

@ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE) 
@JoinColumn(name = "username") 
private Login login; 
+0

是的,我有。沒有。 – kasavbere

+1

這是否正確,那friendshiptype.login字段包含用戶名而不是登錄ID? – szhem

+0

這是真的。但我已將其更改爲'@JoinColumn(name =「login_id」)',它在數據庫模式中反映爲'CONSTRAINT FK_friendshiptype_login_id FOREIGN KEY(login_id)REFERENCES login(id)'。但我仍然得到錯誤。 – kasavbere

2

你可以讓你@JoinColumn非更新:

@JoinColumn(name = "login_id", updatable = false) // or 
@JoinColumn(name = "username", referencedColumnName = "username", updatable= false) 

或tr y以刷新/合併FriendshipType之前再次讀取您的Login實體:

// either this 
entityManager.refresh(friendship.getLogin()); 
// or this 
final Login login = entityManager 
      .getReference(Login.class, friendship.getLogin().getId()); 
friendship.setLogin(login); 
// and then 
entityManager.merge(friendship); 

但是,正如其他建議我相信這FriendshipType將由@ManyToOne關係,或可能由EmbeddableElementCollection


更好地表示

更新

另一個選擇是改變擁有方:

public class Login implements java.io.Serializable { 
    @OneToOne(fetch = FetchType.LAZY) 
    @JoinColumn(name = "friendshiptype_id") 
    private FriendshipType friendshipType; 
    // Other stuff 
} 

public class FriendshipType implements java.io.Serializable { 
    @OneToOne(fetch=FetchType.LAZY, mappedBy="friendshipType") 
    private Login login; 
    // Other stuff 
} 

這會影響你的數據模型(登錄表將有一個friendshiptype_id列,而不是周圍的其他方法),但會阻止你得到的錯誤,因爲關係總是由擁有方維護。

0

您可以使用原來的@Id設置。即

@Id 
@GeneratedValue(strategy = IDENTITY) 
@Column(name = "id", unique = true, nullable = false) 
private Integer id; 

你可以,但你並不需要更改爲:

@Id 
@Column(name = "username", unique = true, nullable = false, length = 50) 
private String username; 

關鍵就在於,必須通過從DB加載啓動,通過em.find(...)或em.createQuery(...)。然後,該id將保證從數據庫中獲得正確的值。

然後,您可以通過結束事務(對於會話bean中的事務範圍實體管理器)或通過調用em.detach(ent)或em.clear()或通過序列化實體來分離實體,通過網絡傳遞。

然後你可以更新實體,一直保持原始的id值。

然後你可以調用em.merge(ent),你仍然會有正確的id。但是,我相信實體必須已經預先存在於實體管理器的持久化上下文中,否則它會認爲您有一個新的實體(使用手動填充的ID),並嘗試對事務刷新/提交進行INSERT。

所以第二個技巧是確保實體在合併點(通過em.find(...)或em.query(...)再次加載,如果您有新的持久性上下文並且不是原來的)。

:-)