Spring Data REST - 如何在投影中包含计算数据?

6

我有以下已定义的域类。

贷款类

@Data
@Entity
public class Loan {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String loanTitle;


    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "loan_id")
    private List<Allowance> allowances;
}

津贴类

@Data
@Entity
public class Allowance {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    private AllowanceType allowanceType;


    private Double allowanceAmount;

}

我还为贷款类定义了一个投影接口,如下所示:

@Projection(name = "studyLoanSingle", types = {Loan.class})
public interface LoanProjection {

    String getLoanTitle();

    List<AllowanceProjection> getAllowances();

}

现在我想在预测中包含贷款的总金额(通过对津贴列表进行迭代计算)并将其发送到UI。在Spring Data REST中是否可能实现此操作?


可以的。请问您能否在问题中包含您的REST控制器代码片段?这很容易做到您所需的事情。将代码放在问题中,我可以帮助您。 - andreybleme
通常情况下,您需要在伪属性上使用@Value - chrylis -cautiouslyoptimistic-
@chrylis 在 @Value 注释中的括号里应该填什么? - Charlie
@andreybleme 因为我正在使用Spring Data REST,所以没有Rest Controller。Spring会在运行时实现控制器。 - Charlie
2个回答

14

这里

你可以使用SpEL表达式在Projection中为暴露的属性注释@Value,以暴露合成属性。甚至调用其他Spring bean上的方法并将目标交给它以在高级计算中使用

因此,您需要创建一个LoanRepo bean方法(例如),该方法计算给定贷款的总金额:

@Query("select sum(a.allowanceAmount) as amount from Loan l join l.allowances a where l = ?1")
Double getTotalAmountByLoan(Loan loan);

并且像这样使用 Projection:

@Projection(name = "totalAmount", types = Loan.class)
public interface LoanTotalAmount {

    @Value("#{target}")
    Loan getLoan();

    @Value("#{@loanRepo.getTotalAmountByLoan(target)}")    
    Double getAmount();
}

那么您可以获得总额贷款:

GET http://localhost:8080/api/loans?projection=totalAmount

看起来一切都很好,但我们这里有一个“小”问题——对于结果中的每个记录,我们都会得到一个额外的查询来计算总金额。因此,您在这里面临着“N+1查询问题”。

我对SDR中这个问题的调查结果以及使用投影的解决方案可以在这里找到。


非常感谢您的帮助。借鉴了这篇文章的思路,我在Loan类中编写了一个getLoanTotal方法,并在投影中调用了该方法,解决了我的问题。 - Charlie
N+1查询使这种方法无法使用,但仍然很酷,Spring的能力真是非常强大! :D - Jan Zyka
2
您可以为分页结果设置单独的投影,以避免N + 1查询问题。 - Charlie

3

将解决视图表示(投影)的方法放置在域对象上并不是最佳解决方案。

将其放置在仓库中对于简单的用例非常有用,对于复杂的问题,可以利用Java 8接口的default方法使用这个简单的技巧。

@Projection(name = "studyLoanSingle", types = Loan.class)
public interface LoanProjection {

    String getLoanTitle();

    //If no need Allowances on json
    @JsonIgnore
    List<Allowance> getAllowances();

    public default Double getAmount() {
        Double result = new Double(0);
        for (Allowance a : getAllowances()) {
           result += a.getAllowanceAmount();            
        }
        return result;
    }
}

你是对的。实际上,在领域对象上进行这些计算会减慢存储库查询的速度,因为每次加载实体时都会进行这些计算,无论它们是否需要。 - Charlie
最终,Spring Data JPA在官方文档中引入了这个选项:“对于非常简单的表达式,一种选择可能是使用默认方法(在Java 8中引入)”。 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.interfaces.open - pdorgambide

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接