【问题标题】:Bidirectional OneToMany relationship with composite primary key与复合主键的双向 OneToMany 关系
【发布时间】:2021-04-06 22:21:38
【问题描述】:

我正在尝试创建双向 OneToMany 关系,
其中一个外键是复合主索引的一部分。

我目前有这个架构:

CREATE TABLE users
(
    id INTEGER UNIQUE AUTO_INCREMENT,

    PRIMARY KEY (id)
);

CREATE TABLE user_roles
(
    name   VARCHAR(16) NOT NULL,
    userId INTEGER,

    PRIMARY KEY (userId, name),
    FOREIGN KEY (userId) REFERENCES users (id)
);

我想在休眠中表示它。

我的 UserEntity 被映射:

@Entity
@Table(name = "users")
public class UserEntity implements User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.userId")
    List<UserRoleEntity> roles;

    @Override
    public Integer getId() {
        return id;
    }

    @Override
    public List<String> getRoles() {
        return roles.stream()
                .map(UserRoleEntity::getName)
                .collect(Collectors.toList());
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setRoles(List<UserRoleEntity> roles) {
        this.roles = roles;
    }
}

还有我的 UserRoleEntity:

@Embeddable // I haven't yet implemented hashcode etc
class UserAndRoleNameUniqueId implements Serializable { 
    protected Integer userId;
    protected String name;

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@Entity
@Table(name = "user_roles")
public class UserRoleEntity {
    @EmbeddedId
    protected UserAndRoleNameUniqueId id = new UserAndRoleNameUniqueId();

    @ManyToOne
    @MapsId("userId")
    protected UserEntity user;

    public void setUserId(Integer userId) {
        this.id.userId = userId;
    }

    public Integer getUserId() {
        return user.getId();
    }

    public void setUser(UserEntity user) {
        this.user = user;
    }

    public String getName() {
        return id.name;
    }

    public void setName(String name) {
        this.id.name = name;
    }
}

我通过以下方式保存 UserEntity 对象:

            var userEntity = new UserEntity();

            userEntity.setRoles(
                    user.getRoles().stream().map(role -> {
                        var userRoleEntity = new UserRoleEntity();
                        userRoleEntity.setName(role);
                        return userRoleEntity;
                    }).collect(Collectors.toList())
            );

            sessionFactory.getCurrentSession().save(userEntity);
            return userEntity;

我得到Caused by: java.sql.SQLException: Unknown column 'userroleen_.user_id' in 'field list'

我怎样才能修复它并以简单的方式实现这种映射?

【问题讨论】:

    标签: java mysql hibernate foreign-keys composite-primary-key


    【解决方案1】:

    要将UserEntity 的主键与UserRoleEntity 中的复合主键列映射,您必须映射UserRoleEntityId 的userId 字段。

    首先,您需要更改 UserEntity 中的映射,现在您已经完成

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.userId")
    List<UserRoleEntity> roles;
    

    你需要改成这个

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
    List<UserRoleEntity> roles;
    

    原因是用户类在这里是父类,我们需要在子实体中指定字段来驱动两者之间的关系。

    现在,在您的子表 UserRoleEntity 中映射很好,它会将复合主键中的 userId 字段映射到 UserEntity 表中的字段 userId,所以本质上您的用户实体主键将共享。

    现在,您必须注意这里的几件事

    • 您需要在复合主键中实现 equals 和 hashcode,否则您的代码生成的所有新 UserRoleEntity 实体都将被视为 equals,因为这两个字段都将为空。
    • 另外,您不应像在上面的代码中那样分配复合主键,您必须指定至少一个不同的字段(这里userId 和name 应该是唯一的组合)
    • 由于您与UserEntity 表共享主键,因此您需要注意,在创建新用户时,只有在休眠执行插入语句后才可用(除非它是手动生成的键,这不是这里的情况),所以UserRoleEntityId 中的name 属性在添加新角色时必须是唯一的。

    最后但并非最不重要的一点

    您应该在一个操作中以双向关系同步您的两个实体。

    1所以您正在使用 setter 将 role 设置为 UserEntity,但您应该有一个以给定方式编写的 add 函数或 setter

    public void add(UserEntityRole role) {
        roles.add(role);
        role.setUser(this);
    }
    

    否则,如果客户端忘记在两个实体上调用setter并且表约束不存在,那么它可以将外键保存为空。

    脚注


    1有关此检查链接文章的更多信息。

    Best way to map hibernate bidirectional relationship

    【讨论】:

      【解决方案2】:
          @ManyToOne
          @MapsId("id")
          protected UserEntity user;
      

      @MapsId 引用另一个实体的 id 字段。因此,如果您将 @MapsId 放在 UserRoleEntity 上,那么您引用的 id 就是来自 UserEntity 的 ID

      但以下内容似乎也很糟糕

          @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.userId")
          List<UserRoleEntity> roles;
      

      改成

          @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id")
          List<UserRoleEntity> roles;
      

      【讨论】:

        猜你喜欢
        • 2012-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-09-23
        • 2021-06-15
        • 2018-01-31
        相关资源
        最近更新 更多