使用JPA实现Specification规范规格模式
由DDD之父 Eric Evans 和OO之父 Martin Fowler定义的规范(Specification也称规格模式)模式article 越来越受到广泛应用,本文介绍如何使用JavaEE 持久层规范JPA实现规格模式,其实现思想也适合其他持久层框架。案例源码见GitHub。
在这个文章中,我们将使用以下POLL类作为创建Specification为例实体。它代表了民意调查,有一个开始和结束日期。在这两个日期之间的时间内用户可以在不同的选择之间投票表决Poll,投票Poll可以在管理员的结束日期尚未到达前锁定。在这种情况下,一个锁日期将被设置。
@Entity
public class Poll {
@Id
@GeneratedValue
private long id;
private DateTime startDate;
private DateTime endDate;
private DateTime lockDate;
@OneToMany(cascade = CascadeType.ALL)
private List votes = new ArrayList<>();
}
现在我们有两个约束:
一个投票poll如果满足startDate < now < endDate,那表示它在进行中。
一个投票poll如果超过100个投票但是没有锁定,表示它很流行。
我们可以通过在POLL投票添加适当的方法如:poll.isCurrentlyRunning(),另外,我们可以使用一个服务方法pollService.isCurrentlyRunning(进行轮询)。然而,我们也希望能够查询数据库获得所有当前正在运行的民意调查。因此,我们可以添加一个DAO或储存repository库的方法类似pollRepository.findAllCurrentlyRunningPolls()
如果我们想结合上述两个约束查询,例如我们想在数据库中查询当前正在运行的所有流行的调查名单?这时Specification模式派上用场。当使用Specification模式时,我们将业务规则转换为额外的类称为Specification。
创建Specification接口和抽象类如下:
public interface Specification {
boolean isSatisfiedBy(T t);
Predicate toPredicate(Root root, CriteriaBuilder cb);
Class getType();
}
abstract public class AbstractSpecification implements Specification {
@Override
public boolean isSatisfiedBy(T t) {
throw new NotImplementedException();
}
@Override
public Predicate toPredicate(Root poll, CriteriaBuilder cb) {
throw new NotImplementedException();
}
@Override
public Class getType() {
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
return (Class) type.getActualTypeArguments()[0];
}
}
specification的核心是isSatisfiedBy() 方法,用于检查一个对象是否满足规范规格要求。. toPredicate()是一个附加的方法,我们在案例中使用它返回一个约束 javax.persistence.criteria.Predicate 实例,它能被用于查询一个数据库。
检查一个poll是否正在运行的规范实现如下:
public class IsCurrentlyRunning extends AbstractSpecification {
@Override
public boolean isSatisfiedBy(Poll poll) {
return poll.getStartDate().isBeforeNow()
&& poll.getEndDate().isAfterNow()
&& poll.getLockDate() == null;
}
@Override
public Predicate toPredicate(Root poll, CriteriaBuilder cb) {
DateTime now = new DateTime();
return cb.and(
cb.lessThan(poll.get(Poll_.startDate), now),
cb.greaterThan(poll.get(Poll_.endDate), now),
cb.isNull(poll.get(Poll_.lockDate))
);
}
}
检查一个投票是否流行的代码如下:
public class IsPopular extends AbstractSpecification {
@Override
public boolean isSatisfiedBy(Poll poll) {
return poll.getLockDate() == null && poll.getVotes().size() > 100;
}
@Override
public Predicate toPredicate(Root poll, CriteriaBuilder cb) {
return cb.and(
cb.isNull(poll.get(Poll_.lockDate)),
cb.greaterThan(cb.size(poll.get(Poll_.votes)), 5)
);
}
}
使用代码:
boolean isPopular = new IsPopular().isSatisfiedBy(poll);
boolean isCurrentlyRunning = new IsCurrentlyRunning().isSatisfiedBy(poll);
为了查询数据库,我们需要继承扩展DAO/Repository来支持规范:
public class PollRepository {
private EntityManager entityManager = ...
public List findAllBySpecification(Specification specification) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// use specification.getType() to create a Root instance
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(specification.getType());
Root root = criteriaQuery.from(specification.getType());
// get predicate from specification
Predicate predicate = specification.toPredicate(root, criteriaBuilder);
// set predicate and execute query
criteriaQuery.where(predicate);
return entityManager.createQuery(criteriaQuery).getResultList();
}
}
我们使用 AbstractSpecification的getType() 方法 创建CriteriaQuery 和 Root 实例. getType()返回的是一个 AbstractSpecification泛型,可以由子类定义. 对于流行IsPopular 且正在进行IsCurrentlyRunning它返回一个Poll class. 如果没有 getType()我们必须在每个规范的 toPredicate() 中实现创建 CriteriaQuery 和 Root实例。它只是减少烦人代码的帮助者。
使用代码:
List popularPolls = pollRepository.findAllBySpecification(new IsPopular());
List currentlyRunningPolls = pollRepository.findAllBySpecification(new IsCurrentlyRunning());
下面问题是如何结合这两个规范,我们如何查询数据库所有流行且还在进行的投票呢?
使用组合模式实现组合规范模式。名称为AndSpecification:
public class AndSpecification extends AbstractSpecification {
private Specification first;
private Specification second;
public AndSpecification(Specification first, Specification second) {
this.first = first;
this.second = second;
}
@Override
public boolean isSatisfiedBy(T t) {
return first.isSatisfiedBy(t) && second.isSatisfiedBy(t);
}
@Override
public Predicate toPredicate(Root root, CriteriaBuilder cb) {
return cb.and(
first.toPredicate(root, cb),
second.toPredicate(root, cb)
);
}
@Override
public Class getType() {
return first.getType();
}
}
使用代码:
Specification popularAndRunning = new AndSpecification<>(new IsPopular(), new IsCurrentlyRunning());
List polls = myRepository.findAllBySpecification(popularAndRunning);
为了提供可读性,我们增加and方法到规范接口
public interface Specification {
Specification and(Specification other);
// other methods
}
abstract public class AbstractSpecification implements Specification {
@Override
public Specification and(Specification other) {
return new AndSpecification<>(this, other);
}
// other methods
}
使用代码:
Specification popularAndRunning = new IsPopular().and(new IsCurrentlyRunning());
boolean isPopularAndRunning = popularAndRunning.isSatisfiedBy(poll);
List polls = myRepository.findAllBySpecification(popularAndRunning);