QueryDSL多参数过滤异常:面积/周长等条件无效求助
问题:QueryDSL多参数图形过滤逻辑失效
需求背景
需实现支持以下条件的图形数据过滤方法:
- 图形类型
- 面积范围(area from、area to)
- 周长范围(circumference from、circumference to)
- 创建时间范围(creation date from、to)
- 创建人
- 图形参数范围(from - to)
当前问题
面积、周长及图形参数过滤逻辑失效:
- 传入无匹配参数时返回空JSON
- 执行过滤操作时返回数据库全部图形数据
实体类代码
Shape.java
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="type", discriminatorType = DiscriminatorType.STRING) @Table(name = "shapes") @QueryEntity public class Shape implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(insertable = false, updatable = false) private String type; @Version private int version; @ManyToOne @JoinColumn(name = "user_id") private User createdBy; private LocalDateTime createdAt; private LocalDateTime lastModifiedAt; private String lastModifiedBy; }
Square.java
@Entity @DiscriminatorValue("SQUARE") public class Square extends Shape { private double height; private double area; private double perimeter; @PrePersist @PreUpdate public void calculateAreaAndPerimeter() { area = height * height; perimeter = 4 * height; } }
服务层代码
ShapeManagementService.java
@Service public class ShapeManagementService implements IShapeManagementService { private final ShapeRepository shapeRepository; private final RoleRepository roleRepository; private final List<IShapeService> shapeServices; private final JPAQueryFactory queryFactory; private static final Logger logger = LoggerFactory.getLogger(ShapeManagementService.class); public ShapeManagementService(ShapeRepository shapeRepository, RoleRepository roleRepository, List<IShapeService> shapeServices, JPAQueryFactory queryFactory) { this.shapeRepository = shapeRepository; this.roleRepository = roleRepository; this.shapeServices = shapeServices; this.queryFactory = queryFactory; } @Transactional @Override public ShapeDto saveShape(ShapeRequestDto shapeRequestDto, String username) { IShapeService shapeService = shapeServices.stream() .filter(h -> h.getShape().equalsIgnoreCase(shapeRequestDto.getType())) .findFirst() .orElseThrow(); return shapeService.save(shapeRequestDto, username); } @Override public List<ShapeDto> getFilteredShapes(String createdBy, String type, Double areaFrom, Double areaTo, Double perimeterFrom, Double perimeterTo, LocalDateTime createdAtFrom, LocalDateTime createdAtTo, Double heightFrom, Double heightTo) { QShape shape = QShape.shape; QSquare square = QSquare.square; QCircle circle = QCircle.circle; BooleanBuilder predicate = new BooleanBuilder(); if (createdBy != null) { predicate.and(shape.createdBy.username.eq(createdBy)); } if (type != null) { predicate.and(shape.type.eq(type)); } if (areaFrom != null || areaTo != null || perimeterFrom != null || perimeterTo != null) { predicate.and(shape.instanceOf(Square.class)); } if (areaFrom != null) { predicate.and(square.area.goe(areaFrom)); } if (areaTo != null) { predicate.and(square.area.loe(areaTo)); } if (perimeterFrom != null) { predicate.and(square.perimeter.goe(perimeterFrom)); } if (perimeterTo != null) { predicate.and(square.perimeter.loe(perimeterTo)); } if (heightFrom != null || heightTo != null) { predicate.andAnyOf(square.height.between(heightFrom, heightTo), circle.radius.between(heightFrom, heightTo)); } if (createdAtFrom != null) { predicate.and(shape.createdAt.goe(createdAtFrom)); } if (createdAtTo != null) { predicate.and(shape.createdAt.loe(createdAtTo)); } JPAQuery<Shape> query = queryFactory.selectFrom(shape) .leftJoin(square).on(shape.instanceOf(Square.class)) .leftJoin(circle).on(shape.instanceOf(Circle.class)) .where(predicate) .groupBy(shape.id); // Grupowanie wyników po id figury List<Shape> shapes = query.fetch(); return shapes.stream() .map(this::convertToDto) .collect(Collectors.toList()); } public ShapeDto convertToDto(Shape shape) { ShapeDto shapeDto; if (shape instanceof Square) { shapeDto = convertSquareToDto((Square) shape); // } else if (shape instanceof Circle) { // shapeDto = convertCircleToDto((Circle) shape); } else { shapeDto = new ShapeDto(); } shapeDto.setId(shape.getId()); shapeDto.setType(shape.getType()); shapeDto.setCreatedBy(shape.getCreatedBy().getUsername()); shapeDto.setCreatedAt(shape.getCreatedAt()); shapeDto.setLastModifiedAt(shape.getLastModifiedAt()); shapeDto.setLastModifiedBy(shape.getLastModifiedBy()); return shapeDto; } private SquareDto convertSquareToDto(Square square) { SquareDto squareDto = new SquareDto(); squareDto.setHeight(square.getHeight()); squareDto.setPerimeter(square.getPerimeter()); squareDto.setArea(square.getArea()); return squareDto; } }
SquareService.java
@Component public class SquareService implements IShapeService { private final SquareRepository squareRepository; private final UserRepository userRepository; private final ModelMapper modelMapper; private final SquareFactory squareFactory; private final ShapeRepository shapeRepository; public SquareService(SquareRepository squareRepository, UserRepository userRepository, ModelMapper modelMapper, SquareFactory squareFactory, ShapeRepository shapeRepository) { this.squareRepository = squareRepository; this.userRepository = userRepository; this.modelMapper = modelMapper; this.squareFactory = squareFactory; this.shapeRepository = shapeRepository; } @Transactional @Override public ShapeDto save(ShapeRequestDto shapeRequestDto, String username) { Square square = squareFactory.createSquare(shapeRequestDto, username); Square squareToSave = squareRepository.save(square); SquareDto squareDto = modelMapper.map(squareToSave, SquareDto.class); squareDto.getCreatedBy(); squareDto.setCreatedBy(square.getCreatedBy().getUsername()); squareDto.setArea(square.getArea()); squareDto.setPerimeter(square.getPerimeter()); return squareDto; } @Override public String getShape() { return "SQUARE"; } }
控制器代码
ShapeController.java
@RestController @RequestMapping("/api/v1/shapes") public class ShapeController { private final IShapeManagementService shapeManagementService; public ShapeController(IShapeManagementService shapeManagementService) { this.shapeManagementService = shapeManagementService; } @PreAuthorize("hasRole('ROLE_CREATOR')") @PostMapping public ResponseEntity<ShapeDto> addShape(@RequestBody ShapeRequestDto shapeRequestDto, Principal p) { ShapeDto shapeDto = shapeManagementService.saveShape(shapeRequestDto, p.getName()); return ResponseEntity.status(HttpStatus.CREATED).body(shapeDto); } @GetMapping(value = "/parameters") public ResponseEntity<List<ShapeDto>> getShapes(@RequestParam(required = false) String createdBy, @RequestParam(required = false) String type, @RequestParam(required = false) Double areaFrom, @RequestParam(required = false) Double areaTo, @RequestParam(required = false) Double perimeterFrom, @RequestParam(required = false) Double perimeterTo, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime createdAtFrom, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime createdAtTo, @RequestParam(required = false) Double heightFrom, @RequestParam(required = false) Double heightTo) { List<ShapeDto> filteredShapes = shapeManagementService.getFilteredShapes( createdBy, type, areaFrom, areaTo, perimeterFrom, perimeterTo, createdAtFrom, createdAtTo, heightFrom, heightTo); return ResponseEntity.status(HttpStatus.OK).body(filteredShapes); } }
问题排查与修复方案
核心问题1:子类字段过滤未与类型判断绑定
当前代码中,面积、周长等子类字段的过滤条件直接加入全局predicate,但使用leftJoin时,非对应类型的实体(比如非Square的Shape)的子类字段会为null,导致过滤条件失效,这类实体被错误保留。
修复方式:将子类属性的过滤条件与instanceOf条件嵌套绑定,确保只有对应类型的实体才会应用字段过滤:
// 面积过滤示例 if (areaFrom != null || areaTo != null) { BooleanBuilder squareAreaPred = new BooleanBuilder(); squareAreaPred.and(shape.instanceOf(Square.class)); if (areaFrom != null) { squareAreaPred.and(square.area.goe(areaFrom)); } if (areaTo != null) { squareAreaPred.and(square.area.loe(areaTo)); } predicate.and(squareAreaPred); }
核心问题2:图形参数过滤逻辑错误
当前andAnyOf的写法会导致非Square/Circle的实体跳过参数过滤,且Square实体不满足height条件时,因circle字段为null,条件被忽略,最终未过滤掉不符合的记录。
修复方式:为每个子类的参数过滤单独绑定类型判断,再用or组合:
if (heightFrom != null && heightTo != null) { BooleanBuilder squareHeightPred = new BooleanBuilder(); squareHeightPred.and(shape.instanceOf(Square.class)) .and(square.height.between(heightFrom, heightTo)); BooleanBuilder circleRadiusPred = new BooleanBuilder(); circleRadiusPred.and(shape.instanceOf(Circle.class)) .and(circle.radius.between(heightFrom, heightTo)); predicate.or(squareHeightPred).or(circleRadiusPred); }
核心问题3:多余的分组操作
groupBy(shape.id)完全多余,每个Shape的id唯一,分组不会改变结果,反而影响查询性能,建议直接移除。
修复后的getFilteredShapes方法
@Override public List<ShapeDto> getFilteredShapes(String createdBy, String type, Double areaFrom, Double areaTo, Double perimeterFrom, Double perimeterTo, LocalDateTime createdAtFrom, LocalDateTime createdAtTo, Double heightFrom, Double heightTo) { QShape shape = QShape.shape; QSquare square = QSquare.square; QCircle circle = QCircle.circle; BooleanBuilder predicate = new BooleanBuilder(); if (createdBy != null) { predicate.and(shape.createdBy.username.eq(createdBy)); } if (type != null) { predicate.and(shape.type.eq(type)); } // 面积过滤:仅对Square生效 if (areaFrom != null || areaTo != null) { BooleanBuilder squareAreaPred = new BooleanBuilder(); squareAreaPred.and(shape.instanceOf(Square.class)); if (areaFrom != null) { squareAreaPred.and(square.area.goe(areaFrom)); } if (areaTo != null) { squareAreaPred.and(square.area.loe(areaTo)); } predicate.and(squareAreaPred); } // 周长过滤:仅对Square生效 if (perimeterFrom != null || perimeterTo != null) { BooleanBuilder squarePerimPred = new BooleanBuilder(); squarePerimPred.and(shape.instanceOf(Square.class)); if (perimeterFrom != null) { squarePerimPred.and(square.perimeter.goe(perimeterFrom)); } if (perimeterTo != null) { squarePerimPred.and(square.perimeter.loe(perimeterTo)); } predicate.and(squarePerimPred); } // 图形参数过滤:Square的height或Circle的radius if (heightFrom != null && heightTo != null) { BooleanBuilder squareHeightPred = new BooleanBuilder(); squareHeightPred.and(shape.instanceOf(Square.class)) .and(square.height.between(heightFrom, heightTo)); BooleanBuilder circleRadiusPred = new BooleanBuilder(); circleRadiusPred.and(shape.instanceOf(Circle.class)) .and(circle.radius.between(heightFrom, heightTo)); predicate.or(squareHeightPred).or(circleRadiusPred); } if (createdAtFrom != null) { predicate.and(shape.createdAt.goe(createdAtFrom)); } if (createdAtTo != null) { predicate.and(shape.createdAt.loe(createdAtTo)); } JPAQuery<Shape> query = queryFactory.selectFrom(shape) .leftJoin(square).on(shape.instanceOf(Square.class)) .leftJoin(circle).on(shape.instanceOf(Circle.class)) .where(predicate); List<Shape> shapes = query.fetch(); return shapes.stream() .map(this::convertToDto) .collect(Collectors.toList()); }
内容的提问来源于stack exchange,提问作者maticzak
相关产品推荐
相关产品推荐

