如何使用JPA的@Formula註解?

Hanstrovsky發表於2020-05-27

背景描述

我們經常會在專案中用到一些資料字典,在儲存和傳輸時使用Code,在前端展示時使用Name,這樣做的好處是便於系統維護,比如專案中用到了"醫院"這個名稱,如果後期需求發生變化不叫"醫院"了,改成"醫療機構",假如不使用資料字典,那麼我們程式碼中、資料庫中所有用到"醫院"的地方都要修改,麻煩不說,漏掉一個就是一個小Bug。在處理這個Code/Name的轉化的時候,我思考了幾種處理方式,第一種,使用@ManyToOne註解關聯字典查詢,這樣是最容易想到的方式,但是這種方式得到的結果是字典物件整體包含在查詢到的實體中,我們所需要的只是字典裡的name,所以我嘗試尋找一種直接將字典表裡的name對映到實體物件上的方式。第二種,使用HQL關聯查詢對映到自定義物件,這種方式可以達到我的預期,但是HQL寫起來很麻煩,尤其是當需要關聯查詢的字典特別多的時候。並且我的專案中動態用的是JPA的Specification,如果使用這種方式,那麼專案中的動態查詢需要改寫,也是不小的工作量。第三種,使用@Formula註解的方式,下面重點說說這種方式。

使用介紹

@Formula的作用是計算出一個臨時的屬性值,我們可以利用它,去關聯查詢其他表中的某個欄位為物件的屬性賦值,而這個屬性是不持久化到資料庫中的。

用程式碼描述一下會比較直觀:

建立兩個實體類

班級字典

@Data
@Entity
public class DictClass {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String classCode;//班級編號
    private String className;//班級名稱
}

學生類

@Data
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String name; //名字
    @Column(name = "CLASS_CODE")
    private String classCode;//班級編號
    @Formula("(select d.class_name from dict_class as d where d.class_code = class_code)")
    private String className;//班級名稱

}

@Formula("(select d.class_name from dict_class as d where d.class_code = class_code)")意思就是從資料庫的dict_class表中查詢到class_name欄位的資料,賦值到className屬性上。"="後面的class_code對應的時@Column裡的class_code

持久層

public interface StudentRepo extends JpaRepository<Student, Long> {
    //很持久
}

控制層

@RestController
@RequestMapping("/formula")
@Api(tags = "formula測試介面")
public class FormulaController {
    @Autowired
    StudentRepo studentRepo;
    @GetMapping
    public List<Student> findAll(){
        return studentRepo.findAll();
    }
}

測試一下,獲取成功

注意事項

雖然看起來很簡單,不過還是有好些個需要注意的地方,一言不合就失效。

1.網上好多人說@Formula必須用在屬性上,其實不是的,@Formula 要與@Id註解同時用在屬性上,或者同時用在在get方法上,否則@Formula失效。

2.如果查詢中用到了where,那麼需要給表起一個別名,否則@Formula失效。

3.@Formula與@Transient不能同時使用,否則@Formula失效。

4.使用@Formula註解的屬性不需要在資料庫表中建立與之對應的欄位,並且即使建立了也沒有作用,加上@Column註解也不行。

使用過程中遇到的坑

當持久層使用原生sql查詢時,會造成NPE異常。

在持久層新增

@Query(nativeQuery = true,value = "select * from student")
List<Student> findByNative();

測試一下

這似乎是因為@Formula遮蔽了className欄位,框架獲取@Column對應的name時拿到null導致的,我未能找到具體原因,所以用起來還是很不隨心,不知道大家是否有好的處理方案,歡迎指教!

相關文章