JPAはSpecification patternを使用してデータ照会を行う

8005 ワード

この記事では、JPAでspecification patternを使用してデータベースに必要なデータをクエリーする方法について説明します.主に、JPA Criteria queriesとspecification patternを組み合わせて、リレーショナル・データベースで必要なオブジェクトを取得する方法です.
ここでは主に1つのPollクラス(選挙)を実体クラスとしてspecificationを生成する.このエンティティクラスにはstart dateとend dateがあり、選挙の開始時間と終了時間を表す.その間、ユーザーはvote、すなわち投票を開始することができる.1回の選挙がまだ終了時間に達していないが、Anministratorによって自発的に閉鎖された場合、閉鎖時間をlock dataで表す.

         @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<>();
        }

より読みやすさを向上させるために,ここでは種々のsetterおよびgetterメソッドを省略する.
データベースをクエリーするために2つの制約が必要だと仮定します.
  • pollこの選挙は進行中条件:自発的に閉鎖されず同時にstartdate
  • pollは非常にpopularの条件です:自発的に閉鎖されていない同時にその中の投票は100
  • を超えました
    一般的にはpollを書くか2つの方法がありますisCurrentlyRunning()メソッドまたはpollServicesなどのサービスを使用する.isCurrentlyRunning(poll). しかし、この2つの方法は、1つのpollが進行中であるかどうかを判断することです.もし私たちのニーズがデータベースで進行中のすべてのpollをクエリーする場合、JPAが提供するrepositoryメソッド:pollRepository.を使用する必要があります.findAllCurrentlyRunningPolls().
    次に、JPAが提供するspecification patternを使用してクエリーを行い、上記の2つの制約を組み合わせて閉じられていないpopularのpollを見つける方法について説明します.
    まず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];
      }
    }
    

    ここではgetType()という方法を無視して、後で説明します
    ここで最も重要な方法はisSatisfiedBy()であり、主に私たちのオブジェクトがいわゆるspecificationに合致するかどうかを判断するために使用され、toPredicateはjavaxとして制約を返す.persistence.criteria.Predicateの例です.この制約は主にデータベースをクエリーするときに使用されます.上記について
  • pollこの選挙は進行中条件:自発的に閉鎖されず同時にstartdate
  • pollは非常にpopularの条件です:自発的に閉鎖されていない同時にその中の投票は100
  • を超えました
    この2つのクエリ条件では、isSatisfiedBy(T t)とtoPredicate(Root poll,CriteriaBuilder cb)の2つのメソッドを実装する2つの新しいspecificationクラス(AbstractSpecificationを継承)を生成します.
    **IsCurrentlyRunning**この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))
        );
      }
    }
    

    isSatisfiedBy(Poll poll)では、現在転送されているpollが進行しているかどうかを判断し、toPredicate(Root poll,CriteriaBuilder cb)では、主にJPA's CriteriaBuilderを使用してPredicateインスタンスを構築し、その後、この実力を使用してCriteriaQueryを構築してデータベースを検索することを目的としています.cb.and()は&&と同じです.
    specificationを作成すると、IsPopularはこのpollがpopularであるかどうかを判断します.
    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)), 100)
        );
      }
    }
    

    次に、指定されたpollのインスタンスをテストすると、このpollに基づいてこの2つの制約のspecificationを生成し、条件を満たすかどうかを同時に判断できます.
    boolean isPopular = new IsPopular().isSatisfiedBy(poll);
    boolean isCurrentlyRunning = new IsCurrentlyRunning().isSatisfiedBy(poll);
    

    データベースをクエリーするために倉庫クラスを拡張する必要があります.
    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();
      }
    }
    

    getTypeを使用してCriteriaQueryとRootインスタンスを作成します.getTypeは、サブクラスによって定義されたAbstractSpecificationインスタンスの汎用タイプを返します.IsPopularとIsCurrentlyRunningの場合、Pollクラスが返されます.getType()がない場合は、作成した各仕様のtoPredicate()にCriteriaQueryとRootインスタンスを作成する必要があります.仕様内の重複コードを減らすために、小さな手伝いにすぎません.もしあなたがもっと良い方法を提案したら、勝手にそれを自分の実現に変えてください.
    これまでspecificationは、データベースをクエリーしたり、オブジェクトが特定の条件を満たしているかどうかを確認したりするための制約されたベクターにすぎませんでした.
    この2つの制約を組み合わせると、isrunningとpopularを満たすpollをクエリーするためにデータベースをクエリーする必要があります.この場合、composite specificationsが必要です.composite specificationsでは、異なるspeficationを組み合わせることができます.
    新しいspecificationクラスを作成し、
    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();
      }
    }
    

    AndSpecificationは2つのspecificationをコンストラクタパラメータとし,内部のisSatisfiedBy()とtoPredicate()では論理と操作の組合せによる2つの仕様の結果を返す.
    Specification popularAndRunning = new AndSpecification<>(new IsPopular(), new IsCurrentlyRunning());
    List polls = myRepository.findAllBySpecification(popularAndRunning);
    

    可読性を向上させるためにspecification interfaceにaddメソッドを追加できます.
    public interface Specification {
       
      Specification and(Specification other);
     
      // other methods
    }
    

    AbstractSpecification:
    abstract public class AbstractSpecification implements Specification {
     
      @Override
      public Specification add(Specification other) {
        return new AddSpecification<>(this, other);
      }
       
      // other methods
    }
    

    and()メソッドを使用して複数のspecificationをリンクできるようになりました
    Specification popularAndRunning = new IsPopular().and(new IsCurrentlyRunning());
    boolean isPopularAndRunning = popularAndRunning.isSatisfiedBy(poll);
    List polls = myRepository.findAllBySpecification(popularAndRunning);
    

    必要に応じて、他の複合材料規格(例えば、OrSpecificationまたはNotSpecification)を使用して、specificationをさらに拡張することができる.
    まとめ:specification patternを使用すると、ビジネス・ルールを個別のspecificationクラスに移動します.これらのspecificationカテゴリはcomposite specifications仕様を使用することで簡単に組み合わせることができます.一般的にspecificationは再利用性とメンテナンス性を向上させる.またspecificationではユニットテストを簡単に行うことができます.specification patternの詳細については、英語が上手な学生はEric EvensとMartin Fowlerのこの文章を読むことができます.
    この文章のソースコードは整理中で、後で出します.