Apache DeltaSpike 简介
1. 概述
Apache DeltaSpike 是一个为 Java 项目提供**CDI 扩展集合的项目;**它需要 CDI 实现在运行时可用。
当然,它可以与 CDI 的不同实现一起使用——JBoss Weld 或 OpenWebBeans。它还在许多应用服务器上进行了测试。
在本教程中,我们将专注于最著名和最有用的模块之一——数据模块。
2. DeltaSpike 数据模块设置
Apache DeltaSpike Data 模块用于简化存储库模式的实现。它允许通过为查询创建和执行提供集中逻辑来减少样板代码。
它与Spring Data 项目非常相似。要查询数据库,我们需要定义一个方法声明(没有实现),它遵循定义的命名约定或包含*@Query*注解。实施将由 CDI 扩展为我们完成。
在接下来的小节中,我们将介绍如何在我们的应用程序中设置 Apache DeltaSpike Data 模块。
2.1. 所需的依赖项
要在应用程序中使用 Apache DeltaSpike Data 模块,我们需要设置所需的依赖项。
当 Maven 是我们的构建工具时,我们必须使用:
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-data-module-api</artifactId>
<version>1.8.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-data-module-impl</artifactId>
<version>1.8.2</version>
<scope>runtime</scope>
</dependency>
当我们使用 Gradle 时:
runtime 'org.apache.deltaspike.modules:deltaspike-data-module-impl'
compile 'org.apache.deltaspike.modules:deltaspike-data-module-api'
Apache DeltaSpike Data 模块工件在 Maven Central 上可用:
要使用 Data 模块运行应用程序,我们还需要在运行时可用的 JPA 和 CDI 实现。
尽管可以在 Java SE 应用程序中运行 Apache DeltaSpike,但在大多数情况下,它将部署在应用程序服务器(例如 Wildfly 或 WebSphere)上。
应用服务器拥有完整的 Jakarta EE 支持,因此我们无需再做任何事情。对于 Java SE 应用程序,我们必须提供这些实现(例如,通过向 Hibernate 和 JBoss Weld 添加依赖项)。
接下来,我们还将介绍EntityManager所需的配置。
2.2. 实体管理器配置
Data 模块需要通过CDI 注入EntityManager。
我们可以通过使用 CDI 生产者来实现这一点:
public class EntityManagerProducer {
@PersistenceContext(unitName = "primary")
private EntityManager entityManager;
@ApplicationScoped
@Produces
public EntityManager getEntityManager() {
return entityManager;
}
}
上面的代码假设我们在persistence.xml文件中定义了名为primary的持久性单元。
让我们看下面的定义示例:
<persistence-unit name="primary" transaction-type="JTA">
<jta-data-source>java:jboss/datasources/blogdemo-jee7-seedDS</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="false" />
</properties>
</persistence-unit>
我们示例中的持久性单元使用 JTA 事务类型,这意味着我们必须提供我们将要使用的事务策略。
2.3. 事务策略
如果我们对数据源使用 JTA 事务类型,那么我们必须定义将在 Apache DeltaSpike 存储库中使用的事务策略。我们可以在apache-deltaspike.properties文件中(在META-INF目录下)执行此操作:
globalAlternatives.org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy=org.apache.deltaspike.jpa.impl.transaction.ContainerManagedTransactionStrategy
我们可以定义四种类型的交易策略:
- BeanManagedUserTransactionStrategy
- ResourceLocalTransactionStrategy
- ContainerManagedTransactionStrategy
- EnvironmentAwareTransactionStrategy
它们都实现了 org.apache.deltaspike.jpa.spi.transaction.TransactionStrategy。
这是我们的数据模块所需配置的最后一部分。
接下来,我们将展示如何实现存储库模式类。
3. 存储库类
当我们使用 Apache DeltaSpike 数据模块时,任何抽象类或接口都可以成为存储库类。
我们所要做的就是添加一个带有forEntity属性的@Repository*注解*,该属性定义了我们的存储库应该处理的 JPA 实体:
@Entity
public class User {
// ...
}
@Repository(forEntity = User.class)
public interface SimpleUserRepository {
// ...
}
或使用抽象类:
@Repository(forEntity = User.class)
public abstract class SimpleUserRepository {
// ...
}
数据模块发现具有此类注解的类(或接口),并将处理内部的方法。
定义要执行的查询的可能性很小。我们将在以下部分中一一介绍。
4. 从方法名查询
定义查询的第一种可能性是使用遵循已定义命名约定的方法名称。 如下所示:
(Entity|Optional<Entity>|List<Entity>|Stream<Entity>) (prefix)(Property[Comparator]){Operator Property [Comparator]}
接下来,我们将关注这个定义的每个部分。
4.1. 返回类型
返回类型主要定义我们的查询可能返回多少对象。我们不能将单个实体类型定义为返回值,以防我们的查询可能返回多个结果。
如果有多个具有给定名称的User,则以下方法将引发异常:
public abstract User findByFirstName(String firstName);
反之则不然——我们可以将返回值定义为Collection,即使结果只是一个实体。
public abstract Collection<User> findAnyByFirstName(String firstName);
如果我们将返回值定义为Collection,建议将一个值作为返回类型(例如findAny )的方法名称前缀将被禁止。
上面的查询将返回所有名字匹配的User,即使方法名称前缀暗示了一些不同的东西。
应该避免这种组合(Collection返回类型和建议单个值返回的前缀),因为代码变得不直观且难以理解。
下一节将展示有关方法名称前缀的更多详细信息。
4.2. 查询方法前缀
前缀定义了我们要在存储库上执行的操作。最有用的一个是找到与给定搜索条件匹配的实体。 这个动作有很多前缀,比如findBy、findAny、findAll。详细列表请查看 Apache DeltaSpike 官方文档 :
public abstract User findAnyByLastName(String lastName);
但是,还有其他用于计算和删除实体的方法模板。我们可以count表中的所有行:
public abstract int count();
此外,remove方法模板存在,我们可以将其添加到我们的存储库中:
public abstract void remove(User user);
对countBy 和removeBy方法前缀的支持将在 Apache DeltaSpike 1.9.0 的下一个版本中添加。
下一节将展示我们如何向查询添加更多属性。
4.3. 具有许多属性的查询
在查询中,我们可以使用and运算符组合的许多属性。
public abstract Collection<User> findByFirstNameAndLastName(
String firstName, String lastName);
public abstract Collection<User> findByFirstNameOrLastName(
String firstName, String lastName);
我们可以组合任意数量的属性。也可以搜索嵌套属性,我们将在下面展示。
4.4. 使用嵌套属性查询
查询也可以使用嵌套属性。
在以下示例中,User实体具有地址类型的地址属性,Address实体具有city属性:
@Entity
public class Address {
private String city;
// ...
}
@Entity
public class User {
@OneToOne
private Address address;
// ...
}
public abstract Collection<User> findByAddress_city(String city);
4.5. 查询中的顺序
DeltaSpike 允许我们定义返回结果的顺序。我们可以同时定义升序和降序:
public abstract List<User> findAllOrderByFirstNameAsc();
如上所示,我们所要做的就是在方法名称中添加一个部分,其中包含我们要排序的属性名称和订单方向的短名称。 我们可以轻松组合多个订单:
public abstract List<User> findAllOrderByFirstNameAscLastNameDesc();
接下来,我们将展示如何限制查询结果的大小。
4.6. 限制查询结果大小和分页
当我们想从整个结果中检索几行第一行时,有些用例。这就是所谓的查询限制。使用 Data 模块也很简单:
public abstract Collection<User> findTop2OrderByFirstNameAsc();
public abstract Collection<User> findFirst2OrderByFirstNameAsc();
First和top可以互换使用。
然后,我们可以通过提供两个附加参数来启用查询分页:@FirstResult和*@MaxResult*:
public abstract Collection<User> findAllOrderByFirstNameAsc(@FirstResult int start, @MaxResults int size);
我们已经在存储库中定义了很多方法。其中一些是通用的,应该定义一次并由每个存储库使用。
Apache DeltaSpike 提供了一些基本类型,我们可以使用它们来拥有很多开箱即用的方法。
在下一节中,我们将重点介绍如何做到这一点。
5. 基本存储库类型
为了获得一些基本的存储库方法,我们的存储库应该扩展 Apache DeltaSpike 提供的基本类型。其中有一些,例如EntityRepository、FullEntityRepository等:
@Repository
public interface UserRepository
extends FullEntityRepository<User, Long> {
// ...
}
或者使用抽象类:
@Repository
public abstract class UserRepository extends AbstractEntityRepository<User, Long> {
// ...
}
上面的实现为我们提供了很多方法,而无需编写额外的代码行,因此我们得到了我们想要的——我们大量减少了样板代码。
如果我们使用基本存储库类型,则无需将额外的forEntity 属性值传递给我们的*@Repository*注解。
当我们为存储库使用抽象类而不是接口时,我们就有了创建自定义查询的额外可能性。
抽象基础存储库类,例如 AbstractEntityRepository让我们可以访问字段(通过 getter)或实用方法,我们可以使用它们来创建查询:
public List<User> findByFirstName(String firstName) {
return typedQuery("select u from User u where u.firstName = ?1")
.setParameter(1, firstName)
.getResultList();
}
在上面的示例中,我们使用了typedQuery 实用程序方法来创建自定义实现。
创建查询的最后一种可能性是使用我们将在下面展示的*@Query*注解。
6. @Query注解
要执行的 SQL查询也可以使用*@Query*注解来定义。它与 Spring 解决方案非常相似。我们必须以 SQL 查询作为值向方法添加注解。
默认情况下,这是一个 JPQL 查询:
@Query("select u from User u where u.firstName = ?1")
public abstract Collection<User> findUsersWithFirstName(String firstName);
如上例所示,我们可以通过索引轻松地将参数传递给查询。
如果我们想通过原生 SQL 而不是 JPQL 传递查询,我们需要定义额外的查询属性 – isNative与真值:
@Query(value = "select * from User where firstName = ?1", isNative = true)
public abstract Collection<User> findUsersWithFirstNameNative(String firstName);