Spring Roo 1.2 中的新應用分層和持久化選項

工程 | Stefan Schmidt | 2011 年 9 月 14 日 | ...

Java 企業應用程式可以有多種形式。根據需求,開發者需要決定應用程式需要哪些特定的架構層。直到現在,Spring Roo 一直採用務實的方法,以減少服務門面、倉庫或 DAO 層引入的通常不必要的複雜性。最新發布的 Spring Roo 1.2.0.M1(參見公告)包含了經常請求的對架構層的支援,這些架構層可以根據應用程式的需求進行定製。本文概述了 Roo 新的服務層和倉庫層功能。

Spring Roo Application Layering Support

雖然有一些新的分層和持久化選項可用,但 Roo 預設將繼續支援 JPA Active Record Entity。但是,您可以透過新增更多的服務層或倉庫層來輕鬆更改現有應用程式(詳情如下)。如果您新增新的層,Roo 將分別在消費者層或服務層自動更改其 ITD。例如,對於給定的域型別,Roo 將自動更改您的應用程式,以便在現有的 MVC 控制器、GWT 定位器、整合測試或按需資料中注入並呼叫新的服務層。

持久化層

隨著 Roo 1.2.0.M1 的釋出,Roo 核心現在有三種選項支援資料持久化:JPA Entities(Active Record 風格)、JPA Repositories 和 MongoDB Repositories。對 Spring Data Neo4J 的支援目前正在開發中,很快將作為一個 Roo 外掛可用。

JPA Entities (Active Record 風格)

Active record 風格的 JPA Entities 自 Spring Roo 的第一個版本釋出以來一直是預設設定,並將繼續如此。為了為您的專案配置 JPA 持久化,您可以執行 jpa setup 命令

roo> jpa setup --provider HIBERNATE --database HYPERSONIC_PERSISTENT

這將把您的專案配置為使用 Hibernate 物件關係對映器以及記憶體資料庫 (HSQLDB)。Roo 支援的 Active record 風格 JPA 實體使用 @RooEntity 註解,該註解負責提供一個持久化識別符號欄位及其訪問器和修改器。此外,此註解建立典型的 CRUD 方法以支援資料訪問。

roo> entity --class ~.domain.Pizza

此命令將建立一個 Pizza 域型別以及 active record 風格的方法來持久化、更新、讀取和刪除您的實體。以下示例還包含許多欄位,這些欄位可以直接新增到 Java 原始檔中,或透過 Roo shell 中的 field 命令新增。

@RooJavaBean
@RooToString
@RooEntity
public class Pizza {

    @NotNull
    @Size(min = 2)
    private String name;

    private BigDecimal price;

    @ManyToMany(cascade = CascadeType.ALL)
    private Set<Topping> toppings = new HashSet<Topping>();

    @ManyToOne
    private Base base;
}

JPA Repository

需要倉庫 / DAO 層而不是預設的 Roo JPA“Active Record”基於持久化方法的開發者,可以透過為給定的 JPA 域型別建立一個基於 Spring Data JPA 的倉庫來實現。在透過 jpa setup 命令為您的專案配置 JPA 持久化後,透過使用 Roo 的 @RooJpaEntity 註解標記域型別即可自動提供此功能。

roo> entity --class ~.domain.Pizza --activeRecord false

透過定義 --activeRecord false,您可以選擇退出預設的“Active Record”風格。以下示例還包含許多欄位,這些欄位可以透過 Roo shell 中的 field 命令新增。

@RooJavaBean
@RooToString
@RooJpaEntity
public class Pizza {

    @NotNull
    @Size(min = 2)
    private String name;

    private BigDecimal price;

    @ManyToMany(cascade = CascadeType.ALL)
    private Set<Topping> toppings = new HashSet<Topping>();

    @ManyToOne
    private Base base;
}

有了域型別,您現在可以使用 repository jpa 命令為該型別建立一個新的倉庫

roo> repository jpa --interface ~.repository.PizzaRepository --entity ~.domain.Pizza

這將建立一個利用 Spring Data JPA 的簡單介面定義

@RooRepositoryJpa(domainType = Pizza.class)
public interface PizzaRepository {
}

當然,您也可以手動將 @RooRepositoryJpa 註解簡單地新增到任何介面上,以替代在 Roo shell 中發出 repository jpa 命令。 

新增 @RooRepositoryJpa 註解將觸發一個相當簡單的 AspectJ ITD 的建立,該 ITD 向 PizzaRepository 介面添加了一個 extends 語句,從而產生等效於此介面定義的結果

public interface PizzaRepository extends JpaRepository<Pizza, Long> {}

JpaRepository 介面是 Spring Data JPA API 的一部分,提供了所有開箱即用的 CRUD 功能。

MongoDB 持久化

作為 JPA 持久化的替代方案,Spring Roo 1.2 現在透過利用 Spring Data MongoDB 專案提供 MongoDB 支援。MongoDB 得到 Cloud Foundry 的支援,Cloud Foundry 是免費開發和託管 Spring Roo 應用程式的絕佳場所。除了 MongoDB,您還可以將 MySQL 和 PostgreSQL 與 Cloud Foundry 一起使用。要為 MongoDB 持久化配置專案,您可以使用 mongo setup 命令

roo> mongo setup

這將配置您的 Spring 應用上下文,以使用執行在本地主機和預設埠上的 MongoDB 安裝。可選的命令屬性允許您定義主機、埠、資料庫名稱、使用者名稱和密碼。如果您在 Cloud Foundry 上使用 MongoDB,只需在 'mongo setup' 後新增 --cloudFoundry,這樣 Roo 就可以為您自動配置一切

一旦應用程式配置了 MongoDB 支援,就可以使用 entity mongorepository mongo 命令了

roo> entity mongo --class ~.domain.Pizza

此命令將建立一個使用 @RooMongoEntity 註解的 Pizza 域型別。此註解負責觸發 ITD 的建立,該 ITD 提供一個帶有 Spring Data @Id 註解的欄位以及其訪問器和修改器。以下示例還包含許多欄位,這些欄位可以透過 Roo shell 中的 field 命令新增。

@RooJavaBean
@RooToString
@RooMongoEntity
public class Pizza {

    @NotNull
    @Size(min = 2)
    private String name;

    private BigDecimal price;

    @ManyToMany(cascade = CascadeType.ALL)
    private Set<Topping> toppings = new HashSet<Topping>();

    @ManyToOne
    private Base base;
}

有了域型別,您現在可以使用 repository mongo 命令(或透過將 @RooRepositoryMongo 註解應用於任意介面)為該型別建立一個新的倉庫

roo> repository mongo --interface ~.repository.PizzaRepository --entity ~.domain.Pizza

這將建立一個利用 Spring Data MongoDB 的簡單介面定義

@RooRepositoryMongo(domainType = Pizza.class)
public interface PizzaRepository {

    List<Pizza> findAll();
}

與上面看到的 Spring Data JPA 驅動的倉庫類似,此介面透過一個 ITD 進行增強,該 ITD 引入了 Spring Data API 提供的 PagingAndSortingRepository,並負責提供所有必要的 CRUD 功能。此外,此介面定義了一個“自定義”查詢器,該查詢器不是由 PagingAndSortingRepository 實現提供的:List findAll();。此方法是 Spring Roo 的 UI 腳手架所必需的,並且由 Spring Data MongoDB 提供的查詢構建器機制自動實現。

與使用 JPA 持久化的應用程式類似(關於將 JPA 與 Postgres 一起使用的詳細資訊,請參閱這篇部落格文章),MongoDB 應用程式可以輕鬆部署到 VMware Cloud Foundry。以下指令碼提供了一個 Pizza Shop 示例應用程式(請參閱 /sample/pizzashop.roo)的示例,該應用程式已針對使用 MongoDB 支援的倉庫層進行了調整

// Create a new project.
project com.springsource.pizzashop

// Create configuration for MongoDB peristence 
mongo setup --cloudFoundry true

// Define domain model.
entity mongo --class ~.domain.Base --testAutomatically
field string --fieldName name --sizeMin 2 --notNull --class ~.domain.Base
entity mongo --class ~.domain.Topping --testAutomatically
field string --fieldName name --sizeMin 2 --notNull --class ~.domain.Topping
entity mongo --class ~.domain.Pizza --testAutomatically
field string --fieldName name --notNull --sizeMin 2 --class ~.domain.Pizza
field number --fieldName price --type java.lang.Float
field set --fieldName toppings --type ~.domain.Topping
field reference --fieldName base --type ~.domain.Base
entity mongo --class ~.domain.PizzaOrder --testAutomatically
field string --fieldName name --notNull --sizeMin 2 --class ~.domain.PizzaOrder
field string --fieldName address --sizeMax 30
field number --fieldName total --type java.lang.Float
field date --fieldName deliveryDate --type java.util.Date
field set --fieldName pizzas --type ~.domain.Pizza

// Add layer support.
repository mongo --interface ~.repository.ToppingRepository --entity ~.domain.Topping
repository mongo --interface ~.repository.BaseRepository --entity ~.domain.Base
repository mongo --interface ~.repository.PizzaRepository --entity ~.domain.Pizza
repository mongo --interface ~.repository.PizzaOrderRepository --entity ~.domain.PizzaOrder
service --interface ~.service.ToppingService --entity ~.domain.Topping
service --interface ~.service.BaseService --entity ~.domain.Base
service --interface ~.service.PizzaService --entity ~.domain.Pizza
service --interface ~.service.PizzaOrderService --entity ~.domain.PizzaOrder

// Create a Web UI.
web mvc setup
web mvc all --package ~.web

// Package the application into a war file.
perform package

// Deploy and start the application in Cloud Foundry
cloud foundry login 
cloud foundry deploy --appName roo-pizzashop --path /target/pizzashop-0.1.0.BUILD-SNAPSHOT.war --memory 512
cloud foundry create service --serviceName pizzashop-mongo --serviceType mongodb
cloud foundry bind service --serviceName pizzashop-mongo --appName roo-pizzashop
cloud foundry start app --appName roo-pizzashop

“即時”示例應用程式目前部署在 Cloud Foundry 上:http://roo-pizzashop.cloudfoundry.com/

服務層

開發者還可以選擇在應用程式中建立服務層。預設情況下,Roo 將為一個或多個域實體建立一個服務介面(和實現)。如果給定域實體存在提供持久化的層,例如 Roo 的預設實體層或倉庫層,服務層將透過其介面和實現公開此持久化層提供的 CRUD 功能。

根據 Roo 的約定,所有功能都將透過 AspectJ ITD 引入,從而為開發者提供一個乾淨的畫布,用於實現不自然地適用於域實體的自定義業務邏輯。服務層的其他常見用例包括安全或遠端整合,例如 Web Services。更詳細的討論請參閱 Spring Roo 參考指南中的架構章節

將服務層整合到 Roo 專案中類似於倉庫層,可以透過直接使用 @RooService 註解或在 Roo shell 中使用 service 命令

roo> service --interface ~.service.PizzaService --entity ~.domain.Pizza

此命令將在定義的包中建立 PizzaService 介面,並在同一包中額外建立一個 PizzaServiceImplPizzaServiceImpl 的名稱和包可以透過可選的 --class 屬性進行自定義)。

@RooService(domainTypes = { Pizza.class })
public interface PizzaService {
}

按照 Roo 的約定,預設的 CRUD 方法定義可以在 AspectJ ITD 中找到

void savePizza(Pizza pizza);
Pizza findPizza(Long id);    
List<Pizza> findAllPizzas();    
List<Pizza> findPizzaEntries(int firstResult, int maxResults);   
long countAllPizzas();    
Pizza updatePizza(pizza pizza);
void deletePizza(Pizza pizza);

同樣,PizzaServiceImpl 也相當簡單

public class PizzaServiceImpl implements PizzaService {}

透過 AspectJ ITD,預設情況下,PizzaServiceImpl 型別會使用 @Service@Transactional 註解。此外,ITD 將向目標型別引入以下方法和欄位

@Autowired PizzaRepository pizzaRepository;
    
public void savePizza(Pizza pizza) {
    pizzaRepository.save(pizza);
}

public Pizza findPizza(Long id) {
    return pizzaRepository.findOne(id);
}

public List<Pizza> findAllPizzas() {
    return pizzaRepository.findAll();
}

public List<Pizza> findPizzaEntries(int firstResult, int maxResults) {
    return pizzaRepository.findAll(new PageRequest(firstResult / maxResults, maxResults)).getContent();
}

public long countAllPizzas() {
    return pizzaRepository.count();
}

public Pizza updatePizza(Pizza pizza) {
    return pizzaRepository.save(pizza);
}
    
public void deletePizza(Pizza pizza) {
    pizzaRepository.delete(pizza);
}

如您所見,Roo 將檢測給定域型別是否存在持久化提供者層,並自動注入此元件,以便將所有服務層呼叫委託給該層。如果不存在持久化(或其他“較低級別”)層,服務層 ITD 將只提供方法存根。

總結

使用 Spring Roo 1.2,將架構層或持久化選項新增到新的或現有的 Spring Roo 管理的應用程式幾乎變得微不足道。無需考慮為新的持久化提供者配置應用程式,也無需考慮將新層的引用注入到 Spring MVC 控制器、GWT UI 或整合測試中——Roo 將為您完成一切!

有了 Spring Roo 1.2 中提供的分層支援,我們預計未來會看到更多的持久化提供者。對 Spring Data Neo4J 的倉庫分層整合目前正在開發中,很快將作為一個 Roo 外掛可用。

如果您想輕鬆嘗試這些新功能,為什麼不構建您自己的 MongoDB 支援的 Pizza Shop 應用程式版本,並將其部署Cloud Foundry?多虧了這些新的 Roo 1.2.0.M1 功能,只需幾分鐘即可完成。

鑑於 Spring Roo 1.2.0.M1 是一個里程碑版本,生產專案應繼續使用 Roo 1.1.5。但是,我們相信 Roo 1.2 M1 適用於探索新功能或快速專案。

Roo 團隊始終歡迎社群的反饋。

獲取 Spring 通訊

訂閱 Spring 通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲取支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,一份訂閱即可搞定。

瞭解更多

近期活動

檢視 Spring 社群所有近期活動。

檢視全部