2016-06-13 77 views
0

我嘗試在Java Web項目中爲用戶開發分層模型。 我用Java Spring,JPA和Hibernate實現了數據庫數據對象。 任何「用戶」都有一個具有OneToMany關係的同一類型爲「用戶」的子項列表。Java JPA實體OneToMany同一實體導致彙編程序堆棧溢出

類用戶

@Entity 
@Table(name = "emsusers") 
public class User extends DomainEntity implements Serializable { 

    @Column(name = "username", nullable = false, unique = true) 
    private String username; 

    @Column(name = "password", nullable = false) 
    private String password; 

    @JoinColumn(name = "role", nullable = false) 
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 
    private Role role; 

    @JoinColumn(name = "parent") 
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 
    private User parent; 

    @JoinColumn(name = "children") 
    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 
    private final Set<User> children; 

    @JoinColumn(name = "servers") 
    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) 
    private Set<Server> servers; 

    private User(String username, String password, Role role, User parent, Set<Server> servers) { 

     super(UUID.randomUUID().toString());   

     if (username == null) { 
      throw new IllegalArgumentException("username == null"); 
     } 
     this.username = username; 

     if (password == null) { 
      throw new IllegalArgumentException("password == null"); 
     } 
     this.password = password; 

     if (role == null) { 
      throw new IllegalArgumentException("role == null"); 
     } 
     this.role = role; 
     this.parent = parent; 
     this.children = Sets.newHashSet(); 

     if (servers == null) { 
      this.servers = Sets.newHashSet(); 
     } 
     else { 
      this.servers = Sets.newHashSet(servers); 
     } 
    } 

    public String getUsername() { 
     return username; 
    } 

    public String getPassword() { 
     return password; 
    } 

    public Role getRole() { 
     return role; 
    } 

    public User getParent() { 
     return parent; 
    } 

    public ImmutableSet<User> getChildren() { 
     return ImmutableSet.copyOf(children); 
    } 

    public ImmutableSet<Server> getServers() { 
     return ImmutableSet.copyOf(servers); 
    } 

    public void updatePassword(String oldPassword, String newPassword) { 

     if (!this.password.equals(oldPassword)) { 
      throw new IllegalArgumentException("Wrong old password."); 
     } 

     if (oldPassword.equals(newPassword)) { 
      throw new IllegalArgumentException("New password is equal to old password."); 
     } 

     this.password = newPassword; 
    } 

    public void update(Role role, Set<Server> servers) { 
     this.role = role; 
     this.servers = Sets.newHashSet(servers); 
    } 

    public void addChild(User child) { 
     this.children.add(child); 
    } 

    public void delChild(User child) { 
     this.children.remove(child); 
    } 

    public static User of(String username, String password, Role role, User parent, Set<Server> servers) { 
     return new User(username, password, role, parent, servers); 
    } 

    // Solo per JPA 
    protected User() { 
     this.children = Sets.newHashSet(); 
    } 
} 

現在我定義了一些服務,例如該服務回報從父用戶創建的所有用戶的分頁列表:

服務

@Transactional(readOnly = true) 
    @Override 
    public PageDTO<UserDTO> getListByParent(String id, Pageable page) { 
     final User parent = userRepository.findOne(id); 
     if (parent == null) { 
      throw new IllegalStateException("User parent not exist."); 
     } 

     final Page<User> usersPage = userRepository.findAll(UserSpecifications.withParent(parent), page); 
     return UserAssembler.toDTOPage(usersPage); 
    } 

然後,類UserAssembler實現一些方法,在這種情況下是服務使用兩個關鍵的方法:

轉換用戶到UserDTO

public static UserDTO toDTO(User input) { 
     final UserDTO dto = new UserDTO(); 
     dto.setId(input.getIdentity()); 
     dto.setUsername(input.getUsername()); 
     dto.setPassword(input.getPassword()); 
     dto.setRole(RoleAssembler.toDTO(input.getRole())); 

     if (input.getParent() != null) { 
      dto.setParent(UserAssembler.toDTO(input.getParent())); 
     } 

     if (input.getChildren().isEmpty() == false) { 
      dto.getChildren().addAll(toListDTO(input.getChildren())); 
     } 

     if (input.getServers() != null) { 
      dto.getServers().addAll(ServerAssembler.toListString(input.getServers())); 
     } 

     return dto; 
    } 

轉換設置爲列表

public static List<UserDTO> toListDTO(Set<User> input) { 
     final List<UserDTO> users = new ArrayList<>(); 
     for (User usr : input) { 
      users.add(toDTO(usr)); 
     }   
     return users; 
    } 

正如你所看到的方法 「toDTO」 調用方法 「toListDTO」 ,稱爲方法「toDTO」,這個遞歸導致一個java堆棧溢出錯誤,當一個控制器調用服務來產生json rest api。

我嘗試使用@BatchSize註釋限制OneToMany子項,但未解決錯誤。

你能幫我或建議解決方案嗎?

謝謝。

+0

那麼,一個UserDTO有一個父UserDTO,它具有子UserDTO,它具有一個父UserDTO,它具有子UserDTO等等。所以,是的,這是一個無限遞歸循環。 Hibernate對此無能爲力。你的DTO的設計是個問題。選擇在您的DTO或孩子中擁有父母,但不能同時擁有父母。 –

回答

0

的toDTO方法將循環無限內這些電話,

dto.setParent(UserAssembler.toDTO(input.getParent())); 

toListDTO(Set<User> input) 

第一個代碼調用這反過來又調用toListDTO父toDTO方法,這可以追溯到兒童最初調用父代的toDTO。所以無限循環繼續...

我的建議,不要使用相同的TOTO方法在父,創建另一個方法(toUserDTO)將用戶轉換爲其DTO不包括getParent和getChildren方法。

public static UserDTO toUserDTO(User input) { 
    final UserDTO dto = new UserDTO(); 
    dto.setId(input.getIdentity()); 
    dto.setUsername(input.getUsername()); 
    dto.setPassword(input.getPassword()); 
    dto.setRole(RoleAssembler.toDTO(input.getRole())); 

    if (input.getServers() != null) { 
     dto.getServers().addAll(ServerAssembler.toListString(input.getServers())); 
    } 

    return dto; 
} 

然後在toListDTO,修正環路如

public static List<UserDTO> toListDTO(Set<User> input) { 
    final List<UserDTO> users = new ArrayList<>(); 
    for (User usr : input) { 
     users.add(toUserDTO(usr)); 
    }   
    return users; 
} 

這樣一來,就不會遞歸回toDTO方法無限。