提问者:小点点

带复合(2列)主键的JPA@JoinTable


在 Spring 启动应用程序中,我有以下实体定义:

@Data
@Entity
@Table(name = "users")
public class User {

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

    @JoinTable(name = "userrole", 
        joinColumns = { @JoinColumn(name = "username") },
        inverseJoinColumns = { @JoinColumn(name = "role") }
    )
    @OneToMany(
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    private List<Role> roles;`

我用的是Spring-data-jpa,Hibernate用H2做数据库。问题是spring-data-jpa,hibernate总是生成/创建带有单个列主键的连接表(DDL) 'userrole '。例如“用户名”。因此,如果将诸如{'username ',' user_role'}和{'username ',' admin_role'}之类的记录插入到连接表(' userrole ')中,则下一次插入会因主键“重复”而失败并出错。

我已经尝试在上面的定义中使用这两列,以及下面的变体:

    @OneToMany(
        cascade = CascadeType.ALL, 
        orphanRemoval = true
    )
    @JoinColumns({
           @JoinColumn(name = "username"),
           @JoinColumn(name = "role") })
    private List<Role> roles;`

但是它们导致了相同或更严重的问题,例如,在后者中,即使表创建也会失败,因为只有一列用作可连接的主键。角色只是另一个包含 2 列“角色”和“描述”的表,基本上是一个角色目录。

我们如何向JPA指定@JoinTable应该同时使用“用户名”和“角色”列作为复合主键?

编辑:我试着按照建议使用一个独立的表格/实体,谢谢@ Kamil bóBen

@Data
@Entity
@Table(name = "users")
public class User {

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

    @OneToMany(
        fetch = FetchType.EAGER,
        cascade = CascadeType.ALL, 
        mappedBy = "username",
        orphanRemoval = true
    )
    @ElementCollection
    private List<UserRole> roles;

UserRole是这样定义的

@Data
@NoArgsConstructor
@Entity
@Table(name = "userrole")
public class UserRole {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
    Long id;    
    
    @Column(nullable = false, name = "username", length = 100)
    private String username;

    @Column(nullable = false, name = "role", length = 50)
    private String role;

用户-角色连接表存储库定义为

@Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {

    UserRole findByUsernameAndRole(String username, String role);
    
    List<UserRole> findByUsername(String username); 
        
    List<UserRole> findByRole(String role); 
    
}

诚然,很丑,但它是有效的。不知何故,它似乎使用了正确的findByUsername()方法来检索与用户相关的角色,可能与“mappdBy”子句有关。“黑魔法”!我还需要在JPA、Spring、Spring数据中找到更多方法

edit2:进一步更新:原来的@JoinTable也可以。但是这些关系需要被指定为@ManyToMany

    @ManyToMany(
            fetch = FetchType.EAGER,
            cascade = CascadeType.MERGE
    )
    @JoinTable(name = "usersroles", 
        joinColumns = { @JoinColumn(name = "username") },
        inverseJoinColumns = { @JoinColumn(name = "role") }
    )
    private List<Role> roles = new ArrayList<Role>();

这将为“用户-角色”表创建两列主键

感谢@Roman


共1个答案

匿名用户

如果role只有两列,例如user_id和Role,那么在jpa中的映射方式如下

@ElementCollection
@CollectionTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"))
@Column(name = "role")
List<String> roles = new ArrayList<>();

否则,jpa实际上要求每个实体的标识符和连接列是单独的列,因此角色实体必须有id、user_id和role_name等列。

class Role {
  @Id
  Long id;
  @ManyToOne
  @JoinColumn(name = "user_id", referencedColumnName = "id");
  User user;
  String roleName;
  // Other fields
}

并且在用户实体中

@OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();

进一步阅读