问题:
有没有人知道如何合并而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尝试重新插入外来对象的情况下合并两个对象?
答案 0 :(得分: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的耐心等待。
答案 1 :(得分: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
关系或Embeddable或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
列而不是相反的方式),但会阻止您获得的错误,因为关系始终由拥有方维护。< / p>
答案 2 :(得分:1)
你试过cascade=MERGE
吗?即
@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;
答案 3 :(得分: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;
诀窍是你必须从数据库加载,通过em.find(...)或em.createQuery(...)开始。然后保证id从DB中填充正确的值。
然后,您可以通过结束事务(对于会话bean中的事务范围实体管理器),或通过调用em.detach(ent)或em.clear(),或通过序列化实体和传递来分离实体通过网络。
然后你可以一直更新实体,保持原始的id值。
然后你可以调用em.merge(ent),你仍然可以拥有正确的id。但是,我认为实体必须已经预先存在于实体管理器的持久化上下文中,否则它会认为您有一个新实体(具有手动填充的id),并尝试INSERT事务刷新/提交。
所以第二个技巧是确保实体在合并点加载(再次通过em.find(...)或em.query(...),如果你有一个新的持久化上下文而不是原文)。
: - )