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尝试重新插入外来对象的情况下合并两个对象?

这是我如何解决问题。 我终于想出合并没有解决的原因是因为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非常耐心。

您可以使@JoinColumn不可更新:

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

或者在合并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关系更好地表示,或者可以通过@ManyToOne或ElementCollection来表示


更新

另一种选择是改变拥有方:

 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列而不是相反的方式),但会阻止您获得的错误,因为关系始终由拥有方维护。

你试过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; 

您可以使用原始的@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; 

诀窍是你必须从数据库加载,通过em.find(…)或em.createQuery(…)开始。 然后保证id从DB中填充正确的值。

然后,您可以通过结束事务(对于会话bean中的事务范围实体管理器),或通过调用em.detach(ent)或em.clear(),或通过序列化实体并将其传递给实体来分离实体。网络。

然后,您可以一直更新实体,保留原始ID值。

然后你可以调用em.merge(ent),你仍然会有正确的id。 但是,我认为实体必须已经预先存在于实体管理器的持久化上下文中,否则它会认为您有一个新实体(具有手动填充的id),并尝试INSERT事务刷新/提交。

所以第二个技巧是确保实体在合并点加载(再次通过em.find(…)或em.query(…),如果你有一个新的持久化上下文而不是原始的) 。

🙂