自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

太炸了,三個注解!Spring Boot + JPA代碼量暴減60%

開發(fā) 前端
在Spring Boot結(jié)合JPA進行開發(fā)時,面對復雜查詢或動態(tài)過濾等常見場景,如果未能及時更新自身的技術(shù)知識儲備,就可能陷入編寫大量冗余代碼的困境。

環(huán)境:SpringBoot3.4.2

1. 簡介

在Spring Boot結(jié)合JPA進行開發(fā)時,面對復雜查詢或動態(tài)過濾等常見場景,如果未能及時更新自身的技術(shù)知識儲備,就可能陷入編寫大量冗余代碼的困境。如下問題:

1?? 查詢邏輯分散:動態(tài)條件查詢需在Service層手動拼接CriteriaBuilder或Specification,導致代碼臃腫且難以維護;2?? 重復計算邏輯:如計算總金額、統(tǒng)計關(guān)聯(lián)表數(shù)量等衍生字段,需在實體中編寫冗余的字段或通過DTO層重復查詢;3?? 硬編碼過濾:數(shù)據(jù)權(quán)限控制常通過全局攔截器或硬編碼SQL實現(xiàn),缺乏靈活性且難以擴展;

這些痛點導致代碼量激增、維護成本高昂,且業(yè)務(wù)邏輯與數(shù)據(jù)庫操作強耦合。

解決方案

@Formula、@SQLRestriction (@Where)、@Filter三大注解直擊痛點:

@Formula:實體字段直接映射SQL表達式,替代重復計算邏輯;

@SQLRestriction:實體級原生SQL條件,簡化動態(tài)查詢;

@Filter:參數(shù)化過濾條件,支持會話級控制,告別硬編碼。

用好這3個強大的注解,能讓代碼量暴減60%,性能與可維護性雙提升!

接下來,我們將詳細的介紹這3個注解的詳細應(yīng)用。

2. 實戰(zhàn)案例

2.1 @Formula

通過該注解你可以指定一個用原生SQL編寫的表達式,該表達式用于讀取屬性的值,而不是將該字段映射到數(shù)據(jù)庫中。@Formula映射定義了一個"派生"屬性,當從數(shù)據(jù)庫讀取實體時,該屬性的狀態(tài)是根據(jù)其他列和函數(shù)計算得出的。如下示例:

拼接字段值

private String name;
private BigDecimal price;


@Formula("(concat(name, '/', price))")
private String info ;

該示例中,并不會在數(shù)據(jù)庫中創(chuàng)建info字段,而是通過這里定義的表達式concat(name, '/', price)(concat數(shù)據(jù)庫函數(shù))將name字段值與price字段值通過 "/" 拼接在一起。

當我們執(zhí)行查詢時,sql輸出如下:

org.hibernate.SQL Line:135 - select p1_0.id,p1_0.deleted,
  (concat(p1_0.name, '/', p1_0.price)),p1_0.name,
  p1_0.price,p1_0.stock from product p1_0

將上面的表達式作為select的一部分進行查詢。

我們不僅僅可以寫表達式,我們還可以執(zhí)行SQL語句。

SQL子句

@Formula("(select sum(s.sale_price * s.quantity) from sales_detail s where s.product_id = id)")
private BigDecimal salePrice ;

同樣的該salePrice字段并不會在數(shù)據(jù)庫中創(chuàng)建。當執(zhí)行查詢時,SQL輸出如下:

SELECT
  p1_0.id,
  p1_0.deleted,(
  concat( p1_0.NAME, '/', p1_0.price )),
  p1_0.name,
  p1_0.price,(
  SELECT
    sum( s.sale_price * s.quantity ) 
  FROM
    sales_detail s 
  WHERE
    s.product_id = p1_0.id 
  ),
  p1_0.stock 
FROM
  product p1_0

表達式同樣作為select的一部分進行了子查詢。

對于這種子查詢,我們還是需要結(jié)合自己的場景來決定是否適合通過此種方式進行查詢。

2.2 @SQLRestriction

該注解可以指定一個用原生SQL編寫的約束條件,該約束條件將被添加到為實體或集合生成的SQL中。簡單說,就是可以在對當前實體查詢時動態(tài)添加查詢條件。

@Entity
@Table(name = "product")
@SQLRestriction("deleted = 0")
public class Product {
  // ...其它屬性
  
  /**0: 未刪除, 1: 已刪除*/
  @Column(columnDefinition = "int default 0")
  private Integer deleted ;
}

這里通過@SQLRestriction注解添加了 "deleted = 0",當我們對該實體Product進行查詢時都會在原來SQL中添加該條件。如下SQL執(zhí)行:

SELECT
  p1_0.id,
  p1_0.deleted,
  p1_0.name,
  p1_0.price
  p1_0.stock 
FROM
  product p1_0 
WHERE
  (p1_0.deleted = 0)

我們不僅僅可以在實體類上添加,還可以在集合屬性上添加。

集合屬性上

@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "product_id")
@SQLRestriction("deleted = 0")
private Set<ProductDetail> productDetails = new HashSet<>() ;


@Entity
public class ProductDetail {
  // ...


  /**0: 未刪除, 1: 已刪除*/
  @Column(columnDefinition = "int default 0")
  private Integer deleted ;
}

當我們通過Product實體查詢時,生成SQL如下:

圖片圖片

2.3 @Filter

@SQLRestriction 注解的問題在于,它僅允許我們指定一個不包含參數(shù)的靜態(tài)查詢,并且無法根據(jù)需求動態(tài)啟用或禁用它。@Filter 注解的作用與 @SQLRestriction 類似,但它還可以在會話(session)級別啟用或禁用,并且支持參數(shù)化。

@Entity
@Table(name = "product")
@FilterDef(name = "filterByDeletedAndStock", parameters = {
    @ParamDef(name = "state", type = Integer.class),
    @ParamDef(name = "stock", type = Integer.class)
})
@Filters({
    @Filter(name = "filterByDeletedAndStock", condition = "deleted=:state and stock >:stock")
})
public class Product {}

在這里,我們通過@FilterDef注解,定義了一個名為filterByDeletedAndStock過濾器,并且還定義了2個參數(shù)state和stock。

接著,我們通過@Filters注解,定義了具體的過濾條件,其中name是上面@FilterDef定義的名稱,condition則為執(zhí)行時動態(tài)添加的條件。

要使用定義的@Filter條件,我們這里通過AOP的方式動態(tài)設(shè)置。

首先,我們定義一個注解,只有使用了該注解的方法,才會在執(zhí)行之前開啟過濾過功能。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EnableFilter {
}

接下來,定義切面攔截使用了@EnableFilter注解的方法。

@Component
@Aspect
public class FilterAspect {
  @PersistenceContext
  private EntityManager entityManager;
  @Around("@annotation(com.pack.formula.annotation.EnableFilter)")
  public Object doProcess(ProceedingJoinPoint joinPoint) throws Throwable {
    try {
      // 從其它地方獲取參數(shù)值
      int state = 0 ;
      int stock = 80 ;
      Filter filter = entityManager.unwrap(Session.class).enableFilter("filterByDeletedAndStock");
      filter.setParameter("state", state) ;
      filter.setParameter("stock", stock) ;
      return joinPoint.proceed();
    } catch (Throwable ex) {
      throw ex;
    } finally {
      entityManager.unwrap(Session.class).disableFilter("filterByDeletedAndStock") ;
    }
  }
}

我們這里是模擬,所以@Filter中定義的2個參數(shù)直接寫死了。

業(yè)務(wù)代碼

@EnableFilter
public List<Product> query() {
  return this.productRepository.findAll() ;
}

執(zhí)行后生成的SQL如下:

圖片圖片

動態(tài)添加了查詢條件。

當我們沒有注解時,生成SQL如下:

圖片圖片

責任編輯:武曉燕 來源: Springboot全家桶實戰(zhàn)案例源碼
相關(guān)推薦

2025-03-03 08:49:59

2023-09-27 23:43:51

單元測試Spring

2020-03-31 15:03:56

Spring Boot代碼Java

2023-11-09 08:01:41

Spring緩存注解

2022-07-15 08:52:10

代碼Java設(shè)計模式

2025-02-26 08:03:17

SpringJPAMyBatis

2022-04-28 08:05:05

數(shù)據(jù)庫數(shù)據(jù)庫交互

2023-04-17 23:49:09

開發(fā)代碼Java

2023-09-21 10:31:06

人工智能模型

2024-03-07 12:51:44

代碼CRUD數(shù)據(jù)

2020-11-02 07:00:29

Spring Boo注解自動化

2023-06-02 16:24:46

SpringBootSSM

2020-06-11 09:00:27

SDN網(wǎng)絡(luò)架構(gòu)網(wǎng)絡(luò)

2022-11-10 09:57:24

2022-05-31 08:36:41

微服務(wù)網(wǎng)關(guān)鑒權(quán)

2020-05-12 20:40:58

SQL慢查詢優(yōu)化數(shù)據(jù)庫

2023-04-26 11:14:11

IT領(lǐng)導者遠程工作

2021-06-29 17:19:44

Spring Boot集成Flyway

2024-08-09 08:46:00

Springjar 包YAML

2024-12-20 07:30:00

C++17代碼
點贊
收藏

51CTO技術(shù)棧公眾號