Jmix 的數據訪問層:將 JPA 用到極致

簡介

      對任何企業及應用來說,數據模型都是應用的基石之一。在設計數據模型時,除了需要考慮業務數據的要求之外,還應該思考一些有可能會影響應用程序設計的問題。比如,是否需要數據庫錶行級的安全機制?是否需要使用軟刪除?是否需要 CRUD API,如果需要的話,誰來幹這個枯燥的活?

      事實上,JPA 已經是 Java 應用程序中創建數據模型的標準了。但是 JPA 不提供能實現高級安全機制的接口,也沒有軟刪除,所以有需求的程序員只能自己實現。

      Jmix 框架提供了通用的引擎,可以爲使用 JPA 的應用程序增加額外的功能。在不破壞 JPA 兼容性的情況下,Jmix 框架可以讓您愉快的使用高級數據安全機制,無感的使用軟刪除,並且爲 CRUD 操作提供通用 REST API 以及其他的功能。

      本文將討論 Jmix 中的數據訪問層,談談使用該框架及其工具到底能做什麼,還會討論數據訪問層的一些底層原理。

數據模型,JPA 和 @JmixEntity

      對於熟悉 JPA 的 Java 開發者來說,使用 Jmix 不需要學習任何新的知識。只需要創建 JPA 實體即可。Jmix 也提供了可視化設計器可以幫助完成這一工作。

      在 Jmix 的可視化設計器中,您需要提供實體名稱,選擇 ID 類型(UUID、Long、Integer、String 或者嵌入類),然後選擇需要支持的功能:

  • 實體版本 - 支持樂觀鎖
  • 創建時審計 - 增加創建時間、創建人
  • 更新時審計 - 增加更新時間、更新人
  • 軟刪除 - 增加刪除時間、刪除人

      最後,您會得到類似如下的 JPA 類:

@JmixEntity
@Table(name = "SPEAKER")
@Entity(name = "Speaker")
public class Speaker {
  
   @JmixGeneratedValue
   @Column(name = "ID", nullable = false)
   @Id
   private UUID id;

   @Email
   @Column(name = "EMAIL", unique = true)
   private String email;

   @Column(name = "NAME", nullable = false)
   @NotNull
   private String name;

   @Column(name = "VERSION", nullable = false)
   @Version
   private Integer version;

   @CreatedBy
   @Column(name = "CREATED_BY")
   private String createdBy;

   @LastModifiedBy
   @Column(name = "LAST_MODIFIED_BY")
   private String lastModifiedBy;

   @DeletedDate
   @Column(name = "DELETED_DATE")
   @Temporal(TemporalType.TIMESTAMP)
   private Date deletedDate;

   // 此處省略部分字段
}

      注意,Jmix 並非侵入式的影響你的代碼。生成的實體只是添加了一些註解,不需要實現任何框架特有的接口,更不需要繼承某個類。這樣的非侵入式設計,在實現您應用程序特有的實體關係和架構時,能保持足夠的靈活度以適應你的業務需求。

      Jmix 使用的大部分註解要麼來自 JPA 要麼來自 Spring Boot JPA 庫:@Entity、@Versioned、@CreatedBy、@LastModifiedBy,您之前也許都見過。Jmix 框架依賴 Spring Boot 的具體實現來支持樂觀鎖以及創建和更新審計。

      下面看看數據模型中幾個 Jmix 特有的註解:

  • @JmixEntity - 引導 Jmix 數據訪問層將此類添加至實體倉庫(Entities repository)
  • @JmixGeneratedValue - 表示實體的 ID 屬性必須由 Jmix 生成並指定
  • @DeletedBy  @DeletedDate - 標記字段,當使用軟刪除方案時用來標記實體的刪除狀態

      您也許會問:“什麼是 Jmix 數據訪問層中的實體倉庫?你們是不是有自己的數據訪問引擎?” 答案是也不是。Jmix 框架會存儲應用程序數據模型的額外信息 - metamodel(元模型),並且增加了一個特殊的工具 - DataManager - 用來訪問數據,但是其底層還是用的 JPA。

Metamodel 和 DataManager

      Jmix 基於 Spring boot 框架構建。Boot 以廣泛使用 IoC 模式而聞名天下,該模式意味着將有關 bean 的所有數據存儲在特殊的存儲庫中 - ApplicationContext。除了這個存儲之外,Jmix 將關於應用程序數據模型的所有數據(比如 metadata)存儲在另一個存儲庫中 - Metadata

      Jmix 中 Metadata 是數據訪問層的核心。包含關於實體、屬性、實體關係等等所有的信息。這些信息是在應用程序啓動時對類進行掃描的過程中一次收集的,Jmix 用這些信息來實現神奇的功能:從行級數據安全機制到自動生成的管理界面 UI 的雙向數據綁定。

      爲了有效的使用 Jmix 數據訪問層,需要使用 DataManager 組件。該組件是 JPA 中著名 EntityManager 的包裝類。與 EntityManager 類似,DataManager 能使用 JPQL 查詢語句或者 ID 加載實體,保存或刪除實體以及對所選實例進行計數。

      下面是 DataManager 的一些示例用法。

      通過 ID 加載一個實體,使用 Java 類作爲參數:

  Speaker loadSpeakerById(UUID speakerId) {
      return dataManager.load(Speaker.class) 
            .id(speakerId)                 
            .one();                         
  }

      使用 JPQL 查詢加載數據:

  List<Speaker> loadSpeakesByCompanyEmail() {
      return dataManager.load(Speaker.class)
            .query("select s from Speaker s where s.email like :email")
            .parameter("email", "%@company.com")
            .list();
  }

      DataManager 使用 Metamodel 提供一些額外的功能。是這樣實現的:在對應用程序數據庫的請求交由 EntityManager 執行之前,對請求進行攔截並修改。

       我們仔細看看 Jmix 提供的這些功能。

高級數據安全

      首先需要提到的是 Jmix 的數據安全子系統。框架使用基於角色的安全機制並定義了兩種類型的角色:

         1. 資源角色(Resource role)- 授予訪問實體對象並在系統中執行特定操作的權限:對實體,實體屬性進行 CURD 操作,使用 UI 界面等等。

         2. 行級角色(Row-level role)- 可以限制對錶中特定數據行的訪問,換句話說,即限制訪問某些實體實例。

      下面是資源角色的示例,可以定義實體 CRUD 操作的策略,甚至可以定義針對實體屬性操作的策略:

  @ResourceRole(name = "Speaker Admin", code = "speaker-admin-role")
  public interface SpeakerAdminRole {

     @EntityPolicy(entityName = "Speaker", 
                 actions = {EntityPolicyAction.ALL})
     @EntityAttributePolicy(entityName = "Speaker", 
                          attributes = "{name, email}", 
                          action = EntityAttributePolicyAction.MODIFY)
     void speakersAccess();
  }

      爲了實現這個功能,DataManager 會分析 JPQL,然後依據策略的定義限制該 JPQL 的執行或修改 JPQL 中的屬性列表以符合安全策略。

      如需定義行級角色,您需要制定一個 JPQL 策略,該 JQPL 策略由 where 以及可選的 join 子句組成,用來限制從數據庫表中選取的數據:

  @RowLevelRole(name = "Speaker's Talks",
             code = "speakers-talks-role")
  public interface SpeakersTalksRole {

     @JpqlRowLevelPolicy(entityClass = Talk.class,
                       where = "{E}.speaker.id = :current_user_id")
     void accessibleTalks();
  }

      因此,帶有行級角色用戶發起的所有查詢都會通過這種方式進行轉換:where子句會用 AND 操作符添加在現有的條件之後,join 子句也會添加在現有的之後。如果 JPQL 中沒有這兩個關鍵字,相應的子句會直接添加在後面。

      Metadata 存儲庫在這裏扮演了重要的角色。它能幫助我們正確的解析並修改 JPQL 並能找出受限的屬性和實體,無論使用的是 JPQL 還是 Java 類來定義 DB 查詢。

軟刪除

      某些情況下,有必要在企業級應用中使用軟刪除。我們發佈了 CUBA 博客“是否實施軟刪除,這是個問題!” 專門討論了軟刪除模式的優缺點。Jmix 是下一代 CUBA,因此也支持軟刪除。

      如果實體中有 @DeletedBy  @DeletedDate 註解的字段,則該實體會自動啓用軟刪除模式。對於這些實體,所有 DataManager 中調用的 remove() 都會被轉換成 UPDATE 語句,而所有的 find() 請求都會被修改,添加一個合適的 where 子句用來過濾那些被 “刪除” 的實體。

@JmixEntity
@Table(name = "SPEAKER")
@Entity(name = "Speaker")
public class Speaker {

   @JmixGeneratedValue
   @Column(name = "ID", nullable = false)
   @Id
   private UUID id;

   @DeletedBy
   @Column(name = "DELETED_BY")
   private String deletedBy;

   @DeletedDate
   @Column(name = "DELETED_DATE")
   @Temporal(TemporalType.TIMESTAMP)
   private Date deletedDate;

      // 此處省略部分字段
}

      這裏我們需要 Metadata 來找出哪一列需要更新或者添加到 where 子句中。與 CUBA 不同,Jmix 不要求實體實現一個特定的接口才能支持軟刪除。而只需要將兩個註解添加至實體的兩個字段即可。

      儘管我們用了軟刪除,但是數據並沒有刪掉,因此您還是可以使用 “硬刪除”,如果想看到軟刪除的數據,可以使用 JPA 的 EntityManager 和原生 SQL 查詢。

REST 和 GraphQL

      自動創建 CRUD API 是 Jmix metamodel 的亮點。當您擁有應用程序中所有實體的信息時,創建實體操作的 API 就非常容易了。

      比如,如果有數據模型的元數據,那麼處理下面這個 HTTP 請求將不是難事:

  GET  'http://server:8080/rest/entities/Speaker'

      比如,CRUD 操作的 endpoints 列表是這樣的:

      使用 Jmix 的好處就是您不需要手動實現這些枯燥的 endpoints。由於有 metamodel,所有這些都能自動生成。在開發 “爲前端服務的後端” 時,可以從自動生成的代碼開始逐步構建您自己的特定 REST API。

      如果您的要求比通用 CRUD REST API 更靈活,可以使用 GraphQL。GraphQL 的一個核心概念就是 schema。使用 Jmix 的 metamodel,我們毫不費力就能獲得 GraphQL schema 所需的所有數據。

      也就是說,Jmix 應用程序使用 GraphQL API 是和 REST API 類似,能以通用的方式實現(很快將發佈)。因此,在您的 Jmix 應用程序中添加 GraphQL endpoint,只需在構建腳本中簡單增加一個 starter 即可:

implementation 'io.jmix.graphql:jmix-graphql-starter'

      然後就可以使用類似這樣的查詢了:

{
  speakerCount
  speakerList(limit: 10, offset: 0, orderBy: {name: ASC}) {
    id
    name
    email
  }
}

結論

      只需在 Jmix 中使用熟悉的 JPA 註解定義數據模型,您就能創建一個可用的應用程序。藉助 Jmix 的 metamodel,無需進行額外的編碼即可使用下列功能:

          1. 行級安全機制

          2. 軟刪除

          3. CRUD REST API

          4. GraphQL API

      如您所見,使用 Jmix 創建一個簡單的以數據爲中心的應用程序,並帶有高級功能和管理界面將變得非常容易。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章