【问题标题】:JPA prevent entity from being savedJPA 防止实体被保存
【发布时间】:2016-06-30 16:41:11
【问题描述】:

我有以下数据库架构:

account -< account_role >- role

总结:一个account可以绑定到多个roles,而account_role是一个连接表。 roles 是预定义的,角色会在迁移中插入到数据库中。

不幸的是,在创建 hibernate 的新 account 尝试将 role 也插入到表中,这会导致以下异常:

org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "role_role_name_key"
Detail: Key (role_name)=(CUSTOMER) already exists.

如何配置关系以防止插入?

下面提到的类:

角色

import javax.persistence.*;

import static javax.persistence.EnumType.STRING;
@Entity
@Table(name = "ROLE")
class Role {

  @Id
  @Column(name = "ROLE_NAME")
  @Enumerated(STRING)
  private RoleName role;
  //getters, setters, constructors
}

帐户

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

import static javax.persistence.FetchType.EAGER;
import static javax.persistence.GenerationType.SEQUENCE;

@Entity
@Table(name = "ACCOUNT")
public class Account {

  @Id
  @SequenceGenerator(allocationSize = 1, sequenceName = "ACCOUNT_PK_SEQ", name = "ACCOUNT_PK_SEQ")
  @GeneratedValue(generator = "ACCOUNT_PK_SEQ", strategy = SEQUENCE)
  @Column(name = "ID")
  private Long id;

  @Column(name = "USERNAME")
  private String username;

  @ManyToMany(fetch = EAGER)
  @JoinTable(
      name = "ACCOUNT_ROLE",
      joinColumns = @JoinColumn(name = "USERNAME", referencedColumnName = "USERNAME"),
      inverseJoinColumns = @JoinColumn(name = "ROLE_NAME", referencedColumnName = "ROLE_NAME")
  )
  private Set<Role> roles = new HashSet<>();
  //getters, setters, constructor
}

这是负责保存新帐户的代码:

@Transactional
public Account createNewAccount(Account account, String password) {
  validator.validateNewAccount(account, password);

  String email = account.getUsername().toLowerCase();

  checkIfEmailAlreadyTaken(email);

  LOG.info("Creating new account for username: '{}'.", account.getUsername());

  account.setPassword(encodePassword(password));

  account = repository.save(account);

  return account;
}

Account 类的对象是根据请求自动创建的。

完整的异常堆栈跟踪可以在here找到。

【问题讨论】:

  • 请发布异常的完整堆栈跟踪,以及创建和持久化帐户的代码。确保您对 account_role.role_name 没有唯一约束。
  • @JBNizet,我有以下唯一约束:ALTER TABLE ACCOUNT_ROLE ADD CONSTRAINT ACCOUNT_ROLE_UNQ UNIQUE (USERNAME, ROLE_NAME);,添加了堆栈跟踪。
  • 您没有显示帐户角色的来源。如果它们应该存在,请不要使用新角色(角色名称)。通过 ID 从数据库中获取角色(使用 findOne() 或 getOne())
  • 是的,我正在使用new,但正如您所见,role(枚举)和ID 也是如此。从数据库中获取角色有意义吗? Hibernate 无法识别role 字段ID?
  • 我几乎从不使用非生成的 ID,所以我没有太多经验。我什至不明白为什么 Hibernate 试图保存角色,因为您在关联上没有任何级联注释。但我的经验法则是你应该避免像瘟疫这样的分离对象,如果数据库中存在一行,那么你应该从 JPA 获取相应的托管实体,而不是用一个(或几个不同的)分离对象来表示它.

标签: spring hibernate spring-boot jpa spring-data-jpa


【解决方案1】:

问题是由

引起的
@Id
@Column(name = "ROLE_NAME")
@Enumerated(STRING)

JPA 规范说:

2.1.4 主键和实体标识

主键(或复合主键的字段或属性)应该是以下类型之一:任何 Java 原始类型;任何原始包装类型; java.lang.String; java.util.日期; java.sql.日期。然而,一般来说,近似数字类型(例如,浮点类型)不应该在主键中使用。主键使用非这些类型的实体将不可移植。

所以 @Id 和 @Enumeration 的组合似乎是不允许的,因为您使用枚举作为 id。

使用您当前的代码,您只能编写与枚举 RoleName 具有的元素一样多的角色。这些是系统的预定义角色。 您应该在启动时执行一次(或以其他方式确保它们存在)

添加注释

@Column(insertable=false, updatable=false)

到 Account.roles,这告诉 JPA 提供者在插入或更新帐户时不要插入或更新角色。

这应该可行。

【讨论】:

  • 感谢您的回复和深入分析。我想它会起作用,但是我已经更改了数据库设计并且无法验证它:/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-09
  • 2016-06-19
  • 1970-01-01
  • 1970-01-01
  • 2019-02-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多