開發(fā)效率翻倍!Spring Boot @Formula 注解帶你告別繁瑣 SQL!
在實際項目中,我們常常需要在實體層中展現(xiàn)一些“計算得來的值”——例如折扣價、總金額、平均值或派生狀態(tài)。 若每次都在 Service 層或 Mapper 層手寫 SQL,不僅讓代碼臃腫、可讀性差,還容易在后續(xù)維護中引發(fā)問題。
其實 Hibernate 早就給出了優(yōu)雅的答案:@Formula 注解。 它能讓我們直接在實體屬性中嵌入 SQL 表達式,從而在 ORM 層實現(xiàn)復雜計算與查詢邏輯。 這意味著,我們可以在不增加數(shù)據(jù)庫字段的情況下,讓實體自動攜帶計算結果。
@Formula 是什么?
@Formula 是 Hibernate 提供的一個注解,用來聲明一個由 SQL 表達式計算出的只讀字段。 它不會映射到數(shù)據(jù)庫的實際列,而是在查詢時由 Hibernate 自動計算。
這種機制適用于:
- 派生值(如全名、折扣價)
 - 聚合計算(如訂單總額、平均值)
 - 條件表達式(如狀態(tài)標志)
 
簡單來說,@Formula 能幫我們把一段 SQL 內聯(lián)到實體字段,從而讓計算邏輯與領域模型自然融合。
基礎用法:讓折扣價自動算出來
下面通過一個簡單示例,演示如何讓 Book 實體自動計算折扣價。
示例代碼
package com.icoderoad.formula.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.Formula;
import java.math.BigDecimal;
@Entity
@Table(name = "t_book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String isbn;
    private String description;
    private Integer page;
    private BigDecimal price;
    // 利用 @Formula 直接計算 9 折后的價格
    @Formula("price * 0.9")
    private BigDecimal discountedPrice;
    // Getter / Setter ...
}當 Hibernate 查詢 Book 時,會自動生成如下 SQL:
select
    b.id,
    b.title,
    b.isbn,
    b.description,
    b.page,
    b.price,
    b.price * 0.9 as discountedPrice
from
    t_book b無需手動計算,折扣價字段在加載實體時就自動帶上,簡潔又安全。
高級玩法:在實體中實現(xiàn)聚合查詢
對于涉及多表計算的復雜場景,@Formula 的威力更大。 我們可以使用子查詢來計算關聯(lián)表的總和、計數(shù)或平均值,完全不需要顯式的 @Join。
示例代碼
package com.icoderoad.formula.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import org.hibernate.annotations.Formula;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "x_order")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String orderNo;
    private LocalDateTime orderDate = LocalDateTime.now();
    // 計算訂單項的總金額
    @Formula("(SELECT COALESCE(SUM(oi.quantity * oi.price), 0) FROM x_order_items oi WHERE oi.order_id = id)")
    private BigDecimal totalAmount;
    // 統(tǒng)計訂單項數(shù)量
    @Formula("(SELECT COUNT(*) FROM x_order_items oi WHERE oi.order_id = id)")
    private Integer itemCount;
    // 計算平均單價
    @Formula("(SELECT COALESCE(AVG(oi.price), 0) FROM x_order_items oi WHERE oi.order_id = id)")
    private BigDecimal averageItemPrice;
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<OrderItem> items = new HashSet<>();
}
package com.icoderoad.formula.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import java.math.BigDecimal;
@Entity
@Table(name = "x_order_items")
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private Integer quantity;
    @Column(nullable = false)
    private BigDecimal price;
    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id", nullable = false)
    private Order order;
}生成的 SQL 示例:
select
    o.id,
    o.order_no,
    o.order_date,
    (select coalesce(sum(oi.quantity * oi.price),0) from x_order_items oi where oi.order_id=o.id) as totalAmount,
    (select count(*) from x_order_items oi where oi.order_id=o.id) as itemCount,
    (select coalesce(avg(oi.price),0) from x_order_items oi where oi.order_id=o.id) as averageItemPrice
from
    x_order o這樣我們就能直接在實體中拿到統(tǒng)計值,而不需要額外的 SQL 或 DTO 轉換。
其它實用場景
@Formula 的靈活性極高,它能輕松應對各種派生邏輯:
聚合統(tǒng)計
@Formula("(select count(o.id) from orders o where o.customer_id = id)")
private int orderCount;條件標志
@Formula("(case when status = 'ACTIVE' then true else false end)")
private boolean isActive;派生屬性
@Formula("concat(first_name, ' ', last_name)")
private String fullName;跨表計算
@Formula("(select coalesce(sum(p.amount), 0) from payments p where p.customer_id = id)")
private double totalPayments;業(yè)務規(guī)則標志
@Formula("(case when balance < 0 then true else false end)")
private boolean isOverdrawn;性能與最佳實踐
雖然 @Formula 用起來非常方便,但也有一些注意事項:
使用建議
- 讀多寫少的場景最適合:例如統(tǒng)計類字段或展示性派生屬性;
 - 復雜表達式要謹慎:避免在高并發(fā)查詢中使用嵌套聚合;
 - 明確只讀特性:
@Formula字段不會參與INSERT或UPDATE; - 注意 SQL 方言兼容性:部分數(shù)據(jù)庫的函數(shù)或語法差異可能導致問題;
 - 日志調試時打開 SQL 輸出:有助于分析生成語句性能。
 
不推薦場景
- 涉及業(yè)務邏輯復雜的計算;
 - 需要頻繁更新或寫入字段;
 - 對數(shù)據(jù)庫移植性要求高的系統(tǒng)。
 
結語:用 @Formula 打造簡潔的領域模型
@Formula 是 JPA 世界里的一把利刃,它讓我們能在實體層優(yōu)雅地表達計算邏輯。 無論是聚合查詢、衍生屬性還是條件標識,都能在不破壞 ORM 結構的情況下完成。
如果你希望項目的代碼更干凈、邏輯更集中、查詢更智能, 那么請記得——不要再把 SQL 寫進 Service 層,讓 @Formula 來幫你“做計算”吧!















 
 
 
















 
 
 
 