代码之家  ›  专栏  ›  技术社区  ›  AndreaNobili

在同一个DB表上实现递归关系的Hibernate实体类映射究竟是如何工作的?

  •  0
  • AndreaNobili  · 技术社区  · 4 年前

    我正在使用Spring Data JPA和Hibernate映射开发一个Spring Boot门户网站,我发现在理解其他人实现的以下代码的确切工作方式时遇到了一些困难(它工作得很好,但JPA\Hibernate不是我喜欢的,我缺少了一些东西)。

    在数据库上我有这个 门户用户 表示我的应用程序的用户的表

    enter image description here

    如您所见,此表包含 parent_id 的FK字段 门户用户 表本身。这用于在用户与其父级之间创建递归关系(您可以看到这是一种经典的引用关系:父级是将另一个用户带入系统的用户)。

    这 门户用户 DB表已映射到此 使用者 实体类:

    @Entity
    @Table(name = "portal_user")
    @Getter
    @Setter
    public class User implements Serializable {
         
        private static final long serialVersionUID = 5062673109048808267L;
        
        @Id
        @Column(name = "id")
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        private Integer id;
        
        @Column(name = "first_name")
        @NotNull(message = "{NotNull.User.firstName.Validation}")
        private String firstName;
        
        @Column(name = "middle_name")
        private String middleName;
        
        @Column(name = "surname")
        @NotNull(message = "{NotNull.User.surname.Validation}")
        private String surname;
        
        @Column(name = "sex")
        @NotNull(message = "{NotNull.User.sex.Validation}")
        private char sex;
        
        @Column(name = "birthdate")
        @NotNull(message = "{NotNull.User.birthdate.Validation}")
        private Date birthdate;
        
        @Column(name = "tax_code")
        @NotNull(message = "{NotNull.User.taxCode.Validation}")
        private String taxCode;
        
        @Column(name = "e_mail")
        @NotNull(message = "{NotNull.User.email.Validation}")
        private String email;
        
        @Column(name = "pswd")
        @NotNull(message = "{NotNull.User.pswd.Validation}")
        private String pswd;
        
        @Column(name = "contact_number")
        @NotNull(message = "{NotNull.User.contactNumber.Validation}")
        private String contactNumber;
        
        @Temporal(TemporalType.DATE)
        @Column(name = "created_at")
        private Date createdAt;
        
        @Column(name = "is_active")
        private boolean is_active;
        
        @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
        @JsonManagedReference
        private Set<Address> addressesList = new HashSet<>();
        
        @ManyToMany(cascade = { CascadeType.MERGE })
        @JoinTable(
            name = "portal_user_user_type", 
            joinColumns = { @JoinColumn(name = "portal_user_id_fk") }, 
            inverseJoinColumns = { @JoinColumn(name = "user_type_id_fk") }
        )
        Set<UserType> userTypes;
    
    
        @ManyToOne(fetch = FetchType.EAGER)
        @JsonProperty("subagent")
        private User parent;
    
        public User() {
            super();
            // TODO Auto-generated constructor stub
        }
    
    
        public User(String firstName, String middleName, String surname, char sex, Date birthdate, String taxCode,
                String email, String pswd, String contactNumber, Date createdAt, boolean is_active) {
            super();
            this.firstName = firstName;
            this.middleName = middleName;
            this.surname = surname;
            this.sex = sex;
            this.birthdate = birthdate;
            this.taxCode = taxCode;
            this.email = email;
            this.pswd = pswd;
            this.contactNumber = contactNumber;
            this.createdAt = createdAt;
            this.is_active = is_active;
        }
        
    
    }
    

    尤其是这种关系似乎是由这个领域来处理的:

    @ManyToOne(fetch = FetchType.EAGER)
    @JsonProperty("subagent")
    private User parent;
    

    然后,我使用此服务方法将一个新对象插入 门户用户 表,考虑到 parent_id 可以设置字段:

    @Override
    @Transactional
    public User insertClientUser(User clientUser) throws DuplicateException, SubAgentUserNotExist, NotFoundException {
        String subAgentEmail = null;
        if(clientUser.getParent() != null) subAgentEmail = clientUser.getParent().getEmail();
    
        User checkClientUserExist = this.getUserByemail(clientUser.getEmail());
    
        if (checkClientUserExist != null) {
            String MsgErr = String.format("User %s already registered in the system !!! "
                    + "Impossible to use POST", clientUser.getEmail());
    
            log.warning(MsgErr);
    
            throw new DuplicateException(MsgErr);
        }
    
        log.info(String.format("UserServiceImpl --> insertClientUser client user: %s %s - subagent email: %s ", 
                 clientUser.getFirstName(), clientUser.getSurname(), subAgentEmail));
    
        // check if present because we could add a user without assigned subagent
        if(subAgentEmail != null) {
    
            User subAgentUser = this.getUserByemail(subAgentEmail);
    
            if (subAgentUser == null) {
                String errorMessage = String.format("Subagent user %s doesn't exist in the sistem !!! "
                        + "Impossible to use POST", subAgentEmail);
    
                log.warning(errorMessage);
    
                throw new SubAgentUserNotExist(errorMessage);
            }
    
            clientUser.setParent(subAgentUser);
        }
        
        User insertedClientUser = userRepository.save(clientUser);
        
        return insertedClientUser;
    }
    

    注意: 这个 客户 是我作为的新记录插入的用户 门户用户 DB表,而 子代理用户 是一个仍然存在于这个DB表中的用户,它将是我插入的用户的父级。 所以基本上,在我的 门户用户 表中,我将有一个新记录(客户端用户),该记录具有 parent_id 它将包含子代理用户的ID。

    这种方法的工作原理非常简单:

    1. 检查我必须插入的当前用户是否不存在于 门户用户 桌子如果用户还不存在于表中,则意味着必须插入该用户。

    2. 它检索子代理用户( 父母亲 )调用另一个服务方法并将其设置为 父母亲 属性。

    3. 最终,它将此客户端用户保存到 门户用户 DB表。

    好吧,这一切都很正常,但我不明白Hibernate是如何正确设置 parent_id 字段。这 parent_id 字段包含父对象的PK(检索到的 子代理用户 对象

    如果我探索我的 门户用户 表在执行了之前的服务方法后,我发现了我所期望的:

    enter image description here

    最后一行是插入的对象。正如您所看到的 parent_id FK字段是53,它是同一表中预期父用户的PK。

    它很好用,但我不明白怎么回事。谁告诉Hibernate如何设置此FK的值?特别是我感到困惑,因为似乎没有明确的映射到我的 使用者 实体类,实际上我有:

    @ManyToOne(fetch=FetchType.EAGER)
    @JsonProperty(“子代理”)
    私有用户家长;
    

    那么我错过了什么?它究竟是如何工作的?

    0 回复  |  直到 4 年前
        1
  •  1
  •   durgaprasad.sdp    4 年前

    在Hibernate中,@ManyToOne指定与另一个具有多对一多重性的实体类的单值关联。在这种情况下,它与同一实体相关联。

    ORM将数据从对象模型映射到关系模型,反之亦然。因此,使用实体映射关系。这有助于我们轻松地从父对象遍历到子对象。我们可以构建多个嵌套关系,并且可以通过使用点和链接方法进行遍历来查询或创建对象。

    正如我们所知,每个实体都有自己的生命周期,一旦设置了关联, Hibernate将外键映射到关联实体的主标识符 。 在这里,它分配给作为主键的id列。但是,它可以使用@JoinColumn注释进行自定义。

    下面是Hibernate在创建portal_user表以设置关联后生成的sql。

    alter table portal_user 
           add constraint FKdgqt4pnsjho6u58mtnackj4h8 
           foreign key (parent_id) 
           references portal_user
    

    当subAgentUser实体被设置为clientUser时,如下所示, hibernate在刷新查询时使用parent_id列将记录映射到关联实体(subAgentUser)的主标识符。

     clientUser.setParent(subAgentUser);