关于JPA的merge方法和联合唯一索引无效问题,这是解决方案的完整攻略:
背景
在JPA的实体类中,我们经常会为表添加联合唯一索引来保存不允许重复的数据。比如下面这个例子:
@Entity
@Table(name = "tb_user", schema = "public",
uniqueConstraints = {@UniqueConstraint(columnNames = {"username","email"})})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String email;
//other fields and methods
}
这个实体类定义了一个User表,并在表上添加了联合唯一索引。这样在往表中插入数据时,只有username和email两个字段的组合所代表的数据在表中唯一。
而在实际生产环境中,我们可能会出现这样的需求:当保存数据时,如果唯一索引冲突,我们希望更新数据而不是抛出异常。这时就需要使用到JPA的merge方法。
@PersistenceContext
private EntityManager entityManager;
public User save(User user) {
return entityManager.merge(user);
}
用来更新一个实体对象并返回更新后的实体。但是我们会发现,在更新时并没有生效,即唯一索引依旧会冲突导致抛出异常。
解决方案
要解决这个问题,我们需要在实体类中重写equals和hashCode方法,并使用Hibernate的自定义注解:@NaturalId
@Entity
@Table(name = "tb_user", schema = "public")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NaturalId(mutable = true)
@Column(unique = true)
private String username;
@NaturalId(mutable = true)
@Column(unique = true)
private String email;
//other fields and methods
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(username, user.username) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(username, email);
}
}
重写equals和hashCode方法后,我们需要在id字段上添加注解,将hibernate.use_identifier_rollback设置为false。
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private long id;
private String username;
private String email;
//other fields and methods
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void update(User user) {
User attachedUser = entityManager.getReference(User.class, user.getId());
attachedUser.setEmail(user.getEmail());
attachedUser.setUsername(user.getUsername());
entityManager.merge(attachedUser);
}
其中,attachedUser是JPA管理下的持久态实体,使用getReference方法查找并返回。
示例
下面用一个简单示例说明:
User newUser = new User();
newUser.setUsername("John");
newUser.setEmail("john@example.com");
entityManager.persist(newUser);
User existingUser = new User();
existingUser.setId(newUser.getId());
existingUser.setUsername("Paul");
existingUser.setEmail("john@example.com");
entityManager.merge(existingUser);
在这个例子中,我们先添加了一个username为"John",email为"john@example.com"的用户。接着我们创建了一个ID与"John"重复的用户对象,并将email改为"john@example.com",即"John"的email。当我们使用merge方法对existingUser进行更新时,我们会发现User表中的数据与我们期望的不一致。即虽然username被更新为了"Paul",但是email并没有被修改,这将导致下一次保存时依旧会因为唯一索引冲突而抛出异常。
使用上述解决方案后,我们再次运行代码后会发现,"John"的email字段被成功修改,唯一索引冲突被避免。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JPA merge联合唯一索引无效问题解决方案 - Python技术站