0

问题:

有谁知道如何在不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 尝试重新插入外来对象的情况下合并两个对象?

4

4 回答 4

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 的耐心。

于 2012-10-15T22:14:06.650 回答
2

您可以使您的@JoinColumn不可更新:

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

Login或者在合并之前尝试再次刷新/获取您的实体FriendshipType

// 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列,而不是相反),但会防止您遇到错误,因为关系始终由拥有方维护。

于 2012-10-15T21:41:38.053 回答
1

你试过cascade=MERGE吗?IE

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

更新

另一种可能的选择是使用@ManyToOne(它保存为关联是唯一的)

@ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
@JoinColumn(name = "username")
private Login login;
于 2012-10-15T20:31:46.393 回答
0

您可以使用原始的 @Id 设置来完成。IE

 @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(...) 从 DB 加载。然后保证用数据库中的正确值填充 id。

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

然后您可以一直更新实体,同时保持原始 id 值。

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

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

:-)

于 2012-10-16T02:20:20.350 回答