在Hibernate中更新时刷新多对多关系

kh212irz  于 2023-10-23  发布在  其他
关注(0)|答案(2)|浏览(136)

我有两个实体有多对多关系:
权限(AbstractActiveEntity只有两个字段,Long Id和boolean Active):

@Entity(name = "Permission")
@NoArgsConstructor
@SuperBuilder(toBuilder = true)
@Table(name = "permission")
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Permission extends AbstractActiveEntity {

    String code;

    @ManyToMany(
            mappedBy = "permissions",
            fetch = FetchType.LAZY,
            cascade = {MERGE},
            targetEntity = Role.class
    )
    @ToString.Exclude
    private Set<Role> roles = new HashSet<>();

//equals and hashcode
}

此角色实体:

@Getter
@SuperBuilder(toBuilder = true)
@Entity
@Setter
@ToString
@RequiredArgsConstructor
public class Role extends AbstractActiveEntity {

    private String name;

    @ManyToMany(cascade = {MERGE})
    @JoinTable(
            name = "roles_permissions",
            joinColumns = {@JoinColumn(name = "role_id")},
            inverseJoinColumns = {@JoinColumn(name = "permission_id")}
    )
    private Set<Permission> permissions = new HashSet<>();
//equals and hashcode
}

这是我的更新方法:

public PermissionDto update(@Valid PermissionDto permissionDto) {
        Permission permissionEntity = permissionMapper.toEntity(permissionDto);
        return permissionMapper.toDto(permissionRepository.save(permissionEntity));
    }

更新API:

@PatchMapping
    public PermissionDto update(@RequestBody @Valid PermissionDto permissionDto) {
        return permissionService.update(permissionDto);
    }

PermissionDto:

@Getter
@Setter
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class PermissionDto extends AbstractActiveDto {

    @NotBlank
    private String code;

    private Set<Long> rolesIds = new HashSet<>();

}

职责:

@NoArgsConstructor
@SuperBuilder(toBuilder=true)
@Data
public class RoleDto extends AbstractActiveDto {

    @NotBlank
    private String name;

    @ToString.Exclude
    private Set<Long> permissionsIds;

}

PermissionMapper:

@Mapper(componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.WARN,
        builder = @Builder(disableBuilder = true),
        uses = RoleMapper.class)
public interface PermissionMapper extends EntityMapper<Permission, PermissionDto> {

    @Override
    @Mapping(source = "rolesIds", target = "roles")
    Permission toEntity(PermissionDto dto);

    @Override
    @Mapping(source = "roles", target = "rolesIds")
    PermissionDto toDto(Permission entity);

    @AfterMapping
    default void assignPermissionToRoles(@MappingTarget Permission permission) {
        var permissionIdSet = new HashSet<Permission>();
        permissionIdSet.add(permission);
        permission.getRoles().forEach(role -> role.setPermissions(permissionIdSet));
    }

}

角色Map器:

@Mapper(componentModel = "spring",
        unmappedTargetPolicy = ReportingPolicy.WARN,
        uses = PermissionMapper.class)
public abstract class RoleMapper {

    @Autowired
    private RoleRepository roleRepository;

    public abstract Role toEntity(RoleDto dto);

    public abstract RoleDto toDto(Role entity);

    Set<Role> mapRolesIdsToRoles(Set<Long> rolesIds) {
        return rolesIds.stream()
                       .map(roleId -> roleRepository.findById(roleId).orElseThrow(
                               () -> new ResourceNotFoundException("Role with ID %d not found".formatted(roleId))
                       ))
                       .collect(Collectors.toSet());
    }

    Set<Long> mapRolesToRolesIds(Set<Role> roles) {
        return roles.stream().map(Role::getId).collect(Collectors.toSet());
    }

}

当我试图更新一个已有的权限,而这个权限有一个与之相关的角色时,hibernate不会删除当前的关系并插入新的关系,它只会插入新的关系。
例如,如果我有一个与ID为1的Role相关的权限,我试图通过传递以下JSON来更新它:

{
  "id": 15991,
  "active": true,
  "code": "NEW_ROLE",
  "rolesIds": [
     2
  ]
}

最后,连接表中有两行,一行是roleId 1,另一行是roleId 2。
我希望在每次更新时刷新角色关系。

cu6pst1q

cu6pst1q1#

你们的关系不对称。如果您更改:

@ManyToMany(cascade = {MERGE}, fetch = FetchType.EAGER)
@JoinTable(
        name = "roles_permissions",
        joinColumns = {@JoinColumn(name = "permission_id")},
        inverseJoinColumns = {@JoinColumn(name = "role_id")}
)
@ToString.Exclude
private Set<Role> roles = new HashSet<>();

以及:

@ManyToMany(cascade = {MERGE}, fetch = FetchType.EAGER)
@JoinTable(
        name = "roles_permissions",
        joinColumns = {@JoinColumn(name = "role_id")},
        inverseJoinColumns = {@JoinColumn(name = "permission_id")}
)
@ToString.Exclude
private Set<Permission> permissions = new HashSet<>();

然后更新将正确传播。

rqcrx0a6

rqcrx0a62#

因此,当你试图保存permissionEntity(不获取其引用)时,Hibernate生成了几个查询:
1.按id(15991)查询选择是否插入或更新->已存在!
1.更新permissionEntity的所有列

  1. Set<Role> roles中只有一个项目,这是一个新的对象引用->插入到关系表。
    为了解决这个问题,让我们通过获取实体来更新服务,而不是从dtoMap它们。
Permission permissionEntity = permissionRepository.findOne(permissionDto.getId());
// Update properties by mapper

Set<Role> roles = roleRepository.findByIdIn(permissionDto.getRoleIds());
permissionEntity.getRoles().clear();
permissionEntity.getRoles().addAll(roles);
permissionRepository.save(permissionEntity)

你可以参考这个doc

相关问题