Spring Boot JPA规范动态生成SQL,如何改进它?

发布于 2025-02-06 17:14:28 字数 933 浏览 2 评论 0原文

现在,我已经习惯了Spring-Boot版本2.4.3,我们主要使用Spring-Boot JPA。

public class ReportSpecification implements Specification<Report> {
    private Long userId;
    private String name;

    @Override
    public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        List<Predicate> predicates = Lists.newArrayList();

        if (ObjectUtils.isNotEmpty(userId))
            predicates.add(criteriaBuilder.equal(root.get(Report_.user).get(User_.id), userId));

        if (Strings.isNotBlank(name))
            predicates.add(criteriaBuilder.equal(root.get(Report_.name), name));

        return criteriaBuilder.and(Iterables.toArray(predicates, Predicate.class));
    }
}

这只是示例代码,但实际上更复杂,规范类希望它是通用的,如果此方法中的句子是一个超过10个,因为它的目的是一个对一个规范类的表,它可以动态生成SQL。

我的问题是,如果这种方法中的语句会有很多,所以我希望该代码可以更可读,有人可以给我一些建议,或者我如何使用哪种设计模式来重构此方法,或者有任何方法可以改善它?

Now I'm used to the spring-boot version 2.4.3, and we primarily use the spring-boot JPA.

public class ReportSpecification implements Specification<Report> {
    private Long userId;
    private String name;

    @Override
    public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        List<Predicate> predicates = Lists.newArrayList();

        if (ObjectUtils.isNotEmpty(userId))
            predicates.add(criteriaBuilder.equal(root.get(Report_.user).get(User_.id), userId));

        if (Strings.isNotBlank(name))
            predicates.add(criteriaBuilder.equal(root.get(Report_.name), name));

        return criteriaBuilder.and(Iterables.toArray(predicates, Predicate.class));
    }
}

This is just sample code, but it's actually more complicated, and the Specification class wants it to be generic, that will be more than 10 if sentences in this method because its purpose is one Table to one Specification class, it can generate SQL dynamically.

My question is, there will be so many if statements in this method, so I hope the code can be more readable, can someone give me some advice or how can I refactor this method using which design pattern, or is there any way to improve it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

总以为 2025-02-13 17:14:28

规范是一个小的单点类,一个谓词确定对象是否满足某些标准。每个简单标准实现一项规范是灵活的。如果规则很复杂,则可以将简单规格组合在一起,就像谓词与逻辑运算符结合在一起。

主要思想是将大规格分为可重复使用的小块,并通过复合图案组合。

用户ID的规范:

    public static Specification<Report> byUserId(Long userId) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (ObjectUtils.isNotEmpty(userId)) {
                predicate = criteriaBuilder.equal(root.get("user").get("id"), userId);
            }
            return predicate;
        };
    }

reportname的规范:

    public static Specification<Report> byReportName(String reportName) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (Strings.isNotBlank(reportName)) {
                predicate = criteriaBuilder.equal(root.get("name"), reportName);
            }
            return predicate;
        };
    }

组合规格:

    public static Specification<Report> reportCompositeSpecification(ReportCriteria reportCriteria) {
        return combine(getSpecifications(reportCriteria));
    }
    
    private static <T> Specification<T> combine(Collection<Specification<T>> specifications) {
        Specification<T> combinedSpecification = null;
        for (Specification<T> specification : specifications) {
            if (combinedSpecification == null) {
                combinedSpecification = Specification.where(specification);
            } else {
                combinedSpecification = combinedSpecification.and(specification);
            }
        }
        return combinedSpecification;        
    }

    public static List<Specification<Report>> getSpecifications(ReportCriteria reportCriteria) {
        return Stream.of(byUserId(reportCriteria.getUserId()),
                         byReportName(reportCriteria.getName()))
                     .collect(Collectors.toList());
    }

< < 实现:​​

public class ReportSpecification implements Specification<Report> {

    private final ReportCriteria reportCriteria;

    public ReportSpecification(ReportCriteria reportCriteria) {
        this.reportCriteria = reportCriteria;
    }

    @Override
    public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return reportCompositeSpecification(reportCriteria).toPredicate(root, query, criteriaBuilder);
    }

    public static Specification<Report> reportCompositeSpecification(ReportCriteria reportCriteria) {
        return combine(getSpecifications(reportCriteria));
    }

    private static <T> Specification<T> combine(Collection<Specification<T>> specifications) {
        Specification<T> combinedSpecification = null;
        for (Specification<T> specification : specifications) {
            if (combinedSpecification == null) {
                combinedSpecification = Specification.where(specification);
            } else {
                combinedSpecification = combinedSpecification.and(specification);
            }
        }
        return combinedSpecification;
    }

    public static List<Specification<Report>> getSpecifications(ReportCriteria reportCriteria) {
        return Stream.of(byUserId(reportCriteria.getUserId()),
                         byReportName(reportCriteria.getName()))
                     .collect(Collectors.toList());
    }

    public static Specification<Report> byUserId(Long userId) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (ObjectUtils.isNotEmpty(userId)) {
                predicate = criteriaBuilder.equal(root.get("user").get("id"), userId);
            }
            return predicate;
        };
    }

    public static Specification<Report> byReportName(String reportName) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (Strings.isNotBlank(reportName)) {
                predicate = criteriaBuilder.equal(root.get("name"), reportName);
            }
            return predicate;
        };
    }
}

public class ReportCriteria {
    private Long userId;
    private String name;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

用法的示例:

import static com.test.repository.specification.ReportSpecification.*;

        ReportCriteria reportCriteria = new ReportCriteria();
        reportCriteria.setName("name");
        reportCriteria.setUserId(1L);
        reportRepository.findAll(reportCompositeSpecification(reportCriteria));

        ReportCriteria reportCriteria = new ReportCriteria();
        reportCriteria.setName(null);
        reportCriteria.setUserId(1L);
        return reportRepository.findAll(new ReportSpecification(reportCriteria));

强>完整

import static com.test.repository.specification.ReportSpecification.*;

        ReportCriteria criteria = new ReportCriteria();
        criteria.setName("name");
        criteria.setUserId(1L);
        return reportRepository.findAll(byUserId(criteria.getUserId()).and(byReportName(criteria.getName())));

优点:

  • 您具有灵活的简单规格,可以在其他上下文
  • 解决方案中合并或重复使用这些规格。您只需实施新规范,然后将其注册在Common Collection getspecifications(Report Criteria Report Criteria)

参考文献

规格-Martin Fowler

domain-driend-drive design:在软件中心解决复杂性

< < < < < < br> 也没有共同的联合方法更简单的实现:

public class ReportSpecification implements Specification<Report> {
    private final ReportCriteria reportCriteria;

    public ReportSpecification(ReportCriteria reportCriteria) {
        this.reportCriteria = reportCriteria;
    }

    @Override
    public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return byUserId(reportCriteria.getUserId()).and(byReportName(reportCriteria.getName()))
                .toPredicate(root, query, criteriaBuilder);
    }

    public static Specification<Report> byUserId(Long userId) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (ObjectUtils.isNotEmpty(userId)) {
                predicate = criteriaBuilder.equal(root.get("user").get("id"), userId);
            }
            return predicate;
        };
    }

    public static Specification<Report> byReportName(String reportName) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (Strings.isNotBlank(reportName)) {
                predicate = criteriaBuilder.equal(root.get("name"), reportName);
            }
            return predicate;
        };
    }
}

Specification is a small, single‐purpose class, a predicate that determines if an object does or does not satisfy some criteria. It is flexible to implement one specification per one simple criterion. In cases where the rules are complex, simple specifications can be combined, just as predicates are combined with logical operators.

The main idea is to divide large specification into small reusable pieces and combine them via a composite pattern.

Specification for userId:

    public static Specification<Report> byUserId(Long userId) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (ObjectUtils.isNotEmpty(userId)) {
                predicate = criteriaBuilder.equal(root.get("user").get("id"), userId);
            }
            return predicate;
        };
    }

Specification for reportName:

    public static Specification<Report> byReportName(String reportName) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (Strings.isNotBlank(reportName)) {
                predicate = criteriaBuilder.equal(root.get("name"), reportName);
            }
            return predicate;
        };
    }

Combine specifications:

    public static Specification<Report> reportCompositeSpecification(ReportCriteria reportCriteria) {
        return combine(getSpecifications(reportCriteria));
    }
    
    private static <T> Specification<T> combine(Collection<Specification<T>> specifications) {
        Specification<T> combinedSpecification = null;
        for (Specification<T> specification : specifications) {
            if (combinedSpecification == null) {
                combinedSpecification = Specification.where(specification);
            } else {
                combinedSpecification = combinedSpecification.and(specification);
            }
        }
        return combinedSpecification;        
    }

    public static List<Specification<Report>> getSpecifications(ReportCriteria reportCriteria) {
        return Stream.of(byUserId(reportCriteria.getUserId()),
                         byReportName(reportCriteria.getName()))
                     .collect(Collectors.toList());
    }

Full implementation:

public class ReportSpecification implements Specification<Report> {

    private final ReportCriteria reportCriteria;

    public ReportSpecification(ReportCriteria reportCriteria) {
        this.reportCriteria = reportCriteria;
    }

    @Override
    public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return reportCompositeSpecification(reportCriteria).toPredicate(root, query, criteriaBuilder);
    }

    public static Specification<Report> reportCompositeSpecification(ReportCriteria reportCriteria) {
        return combine(getSpecifications(reportCriteria));
    }

    private static <T> Specification<T> combine(Collection<Specification<T>> specifications) {
        Specification<T> combinedSpecification = null;
        for (Specification<T> specification : specifications) {
            if (combinedSpecification == null) {
                combinedSpecification = Specification.where(specification);
            } else {
                combinedSpecification = combinedSpecification.and(specification);
            }
        }
        return combinedSpecification;
    }

    public static List<Specification<Report>> getSpecifications(ReportCriteria reportCriteria) {
        return Stream.of(byUserId(reportCriteria.getUserId()),
                         byReportName(reportCriteria.getName()))
                     .collect(Collectors.toList());
    }

    public static Specification<Report> byUserId(Long userId) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (ObjectUtils.isNotEmpty(userId)) {
                predicate = criteriaBuilder.equal(root.get("user").get("id"), userId);
            }
            return predicate;
        };
    }

    public static Specification<Report> byReportName(String reportName) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (Strings.isNotBlank(reportName)) {
                predicate = criteriaBuilder.equal(root.get("name"), reportName);
            }
            return predicate;
        };
    }
}

public class ReportCriteria {
    private Long userId;
    private String name;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Example of usage:

import static com.test.repository.specification.ReportSpecification.*;

        ReportCriteria reportCriteria = new ReportCriteria();
        reportCriteria.setName("name");
        reportCriteria.setUserId(1L);
        reportRepository.findAll(reportCompositeSpecification(reportCriteria));

OR

        ReportCriteria reportCriteria = new ReportCriteria();
        reportCriteria.setName(null);
        reportCriteria.setUserId(1L);
        return reportRepository.findAll(new ReportSpecification(reportCriteria));

OR

import static com.test.repository.specification.ReportSpecification.*;

        ReportCriteria criteria = new ReportCriteria();
        criteria.setName("name");
        criteria.setUserId(1L);
        return reportRepository.findAll(byUserId(criteria.getUserId()).and(byReportName(criteria.getName())));

Advantages:

  • you have flexible simple specifications which can be combined or reused in other context
  • solution is extensible. You need just implement the new specification and register it in the common collection getSpecifications(ReportCriteria reportCriteria)

References

Specifications - Martin Fowler

Domain-Driven Design: Tackling Complexity in the Heart of Software

Also more simple implementation without common combine method:

public class ReportSpecification implements Specification<Report> {
    private final ReportCriteria reportCriteria;

    public ReportSpecification(ReportCriteria reportCriteria) {
        this.reportCriteria = reportCriteria;
    }

    @Override
    public Predicate toPredicate(Root<Report> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        return byUserId(reportCriteria.getUserId()).and(byReportName(reportCriteria.getName()))
                .toPredicate(root, query, criteriaBuilder);
    }

    public static Specification<Report> byUserId(Long userId) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (ObjectUtils.isNotEmpty(userId)) {
                predicate = criteriaBuilder.equal(root.get("user").get("id"), userId);
            }
            return predicate;
        };
    }

    public static Specification<Report> byReportName(String reportName) {
        return (root, query, criteriaBuilder) -> {
            Predicate predicate = null;
            if (Strings.isNotBlank(reportName)) {
                predicate = criteriaBuilder.equal(root.get("name"), reportName);
            }
            return predicate;
        };
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文