specification java_使用JPA实现Specification规范模式 -解道Jdon

使用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);


版权声明:本文为weixin_35730840原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。