1、问题出现的背景
权限模块的开发中,采用了RBAC设计模式,数据表间存在这样的关系:用户和角色多对多,角色和权限多对多。
在JPA中使用了@ManyToMany
的注解来自动绑定多对多的表关系,自动操作关联表。
这是用户和角色实体类中配置的多对多关系【问题出现来源于此】
用户实体类代码片段:
角色实体类代码片段:
业务逻辑出现问题的代码片段:
问题就出现在:
Set<SysRole> roles = new HashSet<>();
for (String roleId : roleIdList) {
SysRole role = sysRoleDao.findById(roleId).get();
roles.add(role);
}
2、分析问题出现的原因
在实体类中,我只考虑乐在Json转换的时候,不去转换多对多映射的属性。同时,对于多对多的属性我这里使用了Set<>
集合来存储,考虑的是避免重复。在业务逻辑中,我又再次使用了Set<>
集合来临时存储查询出来的角色信息。
在javaSE核心基础学习时,我了解到HashSet
内部保证存储的角色实体
唯一性的原理是,角色实体类重新实现了hashCode
和equals
方法。
出现堆栈溢出的原因就来源于此,在hashCode
和equals
方法中如果没有特别说明,会对多对多属性也进行处理。而且用户实体类和角色实体类都有互相包含,并且都有实现hashCode
和equals
方法,这就出现了循环的情况。
在执行roles.add(role);
语句的时候,会执行SysRole
的hashCode
和equals
方法,这时根据懒加载机制,就会对users集合中元素进行查询,同理,SysUser
也会执行hashCode
和equals
方法,这时又开始对roles集合中元素进行查询,如此就出现了一个死循环的情况。
3、问题的解决
实体类对于toString
、hashCode
和equals
方法都有相应的注解,其中可以设置来排除一些属性的转换。
故而,添加注解如下:
角色实体类:
@ToString(exclude = {
"users"})
@EqualsAndHashCode(exclude= {
"users"})
因为用户和角色多对多关系里,角色实体被用户实体来维护,所以我们一般在角色实体中不操作用户实体,而在用户实体中可以操作角色实体。故而,我在角色实体类中的toString
、hashCode
和equals
方法都把用户属性排除,对于用户实体,在toString
、hashCode
和equals
方法中都还是处理角色对象。
这里,也是符合常理的做法,这就体现了用户要相同就要保证他的角色也要相同,而对于角色要相同,不用考虑与它关联的用户是否相同。
代码实现
【使用了lombok插件、使用了swagger插件、JPA注解】
- 用户实体类
@Data
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {
@ApiModelProperty(value = "用户id")
@Id
private String id;
@ApiModelProperty(value = "手机")
private String mobile;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "可用状态")
private Integer enableState;
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;
@ApiModelProperty(value = "修改时间")
private Date gmtUpdate;
@ApiModelProperty(value = "小组id")
private String groupId;
@ApiModelProperty(value = "用户等级")
private String level;
@ApiModelProperty(value = "用户头像")
private String staffPhoto;
@ManyToMany
@JsonIgnore
@JoinTable(name="sys_user_role",
joinColumns = {
@JoinColumn(name = "user_id",referencedColumnName = "id")},
inverseJoinColumns = {
@JoinColumn(name = "role_id",referencedColumnName = "id")})
private Set<SysRole> roles = new HashSet<>();
}
- 角色实体类
@Data
@ToString(exclude = {
"users"})
@EqualsAndHashCode(exclude= {
"users"})
@Entity
@Table(name = "sys_role")
public class SysRole implements Serializable {
@ApiModelProperty(value = "角色id")
@Id
private String id;
@ApiModelProperty(value = "角色名称",required = true)
private String name;
@ApiModelProperty(value = "描述")
private String description;
@ApiModelProperty(value = "小组id")
private String groupId;
@ManyToMany(mappedBy = "roles")
@JsonIgnore
private Set<SysUser> users = new HashSet<>();
@ManyToMany
@JsonIgnore
@JoinTable(name = "sys_role_permission",
joinColumns = {
@JoinColumn(name="role_id",referencedColumnName = "id")},
inverseJoinColumns = {
@JoinColumn(name = "permission_id",referencedColumnName = "id")})
private Set<SysPermission> perms = new HashSet<>();
}