java JPA 合并导致重复输入外国实体

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12902827/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 10:43:17  来源:igfitidea点击:

JPA merge results in duplicate entry of foreign entity

javajpamerge

提问by kasavbere

QUESTIONS:

问题:

Does anyone know how to merge without having EntityManagertrying to re-insert the foreign entity?

有谁知道如何在不EntityManager尝试重新插入外国实体的情况下进行合并?

SCENARIO:

设想:

Just to set up a scenario that closely matches my case: I have two entities

只是为了设置一个与我的案例非常匹配的场景:我有两个实体

@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
}

Both the Loginentity and the FriendshipTypeentity are persisted to the database separately. Then, later, I need to merge a Loginrow with a FriendshipTyperow. When I call entityManager.merge(friendship), it tries to insert a new Loginwhich of course results in the following error

无论是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 (?, ?)

My question, again, is how do I merge two objects without having enityManager trying to reinsert the foreign object?

我的问题再次是如何在没有 enityManager 尝试重新插入外来对象的情况下合并两个对象?

回答by kasavbere

Here is how I solve the problem. I finally figure the reason the merge is not resolving is because the login.id is auto generated by JPA. So since I really don't need an auto-generated id field, I remove it from the schema and use usernameas the @idfield:

这是我解决问题的方法。我终于明白合并没有解决的原因是因为 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;
}

Another solution that occurred to me, which I didn't implement but may help someone else, should they need to have an auto-generated id field.

我想到的另一个解决方案,我没有实施,但如果他们需要自动生成的 id 字段,可能会帮助其他人。

Instead of creating an instance of Loginfor the merger, get the instance from the database. What I mean is, instead of

不是Login为合并创建一个实例,而是从数据库中获取实例。我的意思是,而不是

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

Do rather

宁愿做

Login login = loginDao.getByUsername(username);

That way, a new id field is not generated making the entity seem different.

这样,不会生成新的 id 字段,使实体看起来不同。

Thanks and up-votes to everyone for helping, especially to @mijer for being so patient.

感谢并为每个人的帮助点赞,特别是感谢@mijer 的耐心。

回答by Anthony Accioly

You can make your @JoinColumnnon updatable:

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

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

Or try to refresh / fetch your Loginentity again before merging the FriendshipType:

或者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);

But, as other suggested I belive that FriendshipTypewould be better represented by a @ManyToOnerelationship or maybe by a Embeddableor ElementCollection

但是,正如其他人所建议的那样,我相信FriendshipType@ManyToOne关系或EmbeddableElementCollection可以更好地表示



Update

更新

Yet another option is to change the owning side:

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

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 
}

This will affect your data model (login table will have a friendshiptype_idcolumn instead of the other way around), but will prevent the errors that you are getting, since relationships are always maintained by the owning side.

这将影响您的数据模型(登录表将有一个friendshiptype_id列而不是相反的列),但会防止您遇到的错误,因为关系始终由拥有方维护。

回答by szhem

Have you tried cascade=MERGE? I.e.

你试过cascade=MERGE吗?IE

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

UPDATE

更新

Another possible option is to use @ManyToOne(it's save as the association is unique)

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

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

回答by Glen Best

You can do it with your original @Id setup. i.e.

你可以用你原来的@Id 设置来做到这一点。IE

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

You can, but you don't need to change to:

可以,但不需要更改为:

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

The trick is you must start by loading from the DB, via em.find(...) or em.createQuery(...). Then the id is guaranteed to be populated with the right value from the DB.

诀窍是您必须首先通过 em.find(...) 或 em.createQuery(...) 从数据库加载。然后保证用来自数据库的正确值填充 id。

Then you can detach the entity by ending a transaction (for a transaction-scoped entity manager in a session bean), or by calling em.detach(ent) or em.clear(), or by serialising the entity and passing it over the network.

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

Then you can update the entity, all the while, keeping the original id value.

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

Then you can call em.merge(ent) and you will still have the correct id. However, I believe the entity must already pre-exist in the persistent context of the entity manager at this instant, otherwise it will think that you have a new entity (with manually populated id), and try to INSERT on transaction flush/commit.

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

So the second trick is to ensure the entity is loaded at the point of the merge (via em.find(...) or em.query(...) again, if you have a new persistent context and not the original).

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

:-)

:-)