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 社区所有近期活动。

查看全部