バネデータJPAを用いた複数カラム間のデータ検索


このブログ記事は、春のデータJPAを使用して、テーブル内の複数の列のデータを検索する処理を説明します.

技術

  • Spring Boot 2.4.X
  • スプリングデータJPA 2.4.X
  • 角11 .X
  • MySQL
  • バネデータJPAを使用してテーブル内の複数の列を横切ってデータを検索する方法は3つあります
  • 例Matcherの使用
  • 仕様の使用
  • クエリの使用
    従業員とプロジェクトテーブルの例を挙げましょう.クリエイトビューEmployeeProjectView (データベースと同じemployee_project_view ) 従業員とプロジェクトテーブルを結合し、単一のビューでそれらを返す
  • package com.pj.multicolumnsearch.domain;
    
    import lombok.Data;
    import org.springframework.data.annotation.Immutable;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    import java.io.Serializable;
    
    /**
     * @author Pavan Jadda
     */
    @Data
    @Entity
    @Immutable
    @Table(name = "employee_project_view")
    public class EmployeeProjectView implements Serializable
    {
        private static final long serialVersionUID = 1916548443504880237L;
    
        @Id
        @Column(name = "employee_id")
        private Long employeeId;
    
        @Column(name = "last_name")
        private String lastName;
    
        @Column(name = "first_name")
        private String firstName;
    
        @Column(name = "project_id")
        private Long projectId;
    
        @Column(name = "project_name")
        private String projectName;
    
        @Column(name = "project_budget")
        private Double projectBudget;
    
        @Column(name = "project_location")
        private String projectLocation;
    
    }
    
    

    matcherの例


    春の医者によると

    Query by Example (QBE) is a user-friendly querying technique with a simple interface. It allows dynamic query creation and does not require you to write queries that contain field names


    クエリAPIでは、3つの部分から構成されます.
  • プローブ:人口フィールドを持つドメインオブジェクトの実際の例.
  • Examplematcher :特定のフィールドに一致する方法についての詳細を実行します.これは、複数の例間で再利用することができます.
  • 以下に例を示します.クエリの作成に使用します.
  • @Override
    public Page<EmployeeProjectView> findEmployeeProjectsExampleMatcher(EmployeeRequestDTO employeeRequestDTO)
        {
            /* Build Search object */
            EmployeeProjectView employeeProjectView=new EmployeeProjectView();
            employeeProjectView.setEmployeeId(employeeRequestDTO.getEmployeeId());
            employeeProjectView.setLastName(employeeRequestDTO.getFilterText());
            employeeProjectView.setFirstName(employeeRequestDTO.getFilterText());
            try
            {
                employeeProjectView.setProjectId(Long.valueOf(employeeRequestDTO.getFilterText()));
                employeeProjectView.setProjectBudget(Double.valueOf(employeeRequestDTO.getFilterText()));
            }
            catch (Exception e)
            {
                log.debug("Supplied filter text is not a Number");
            }
            employeeProjectView.setProjectName(employeeRequestDTO.getFilterText());
            employeeProjectView.setProjectLocation(employeeRequestDTO.getFilterText());
    
            /* Build Example and ExampleMatcher object */
            ExampleMatcher customExampleMatcher = ExampleMatcher.matchingAny()
                    .withMatcher("firstName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("lastName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("projectId", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("projectName", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("projectLocation", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase())
                    .withMatcher("projectBudget", ExampleMatcher.GenericPropertyMatchers.contains().ignoreCase());
    
            Example<EmployeeProjectView> employeeExample= Example.of(employeeProjectView, customExampleMatcher);
    
            /* Get employees based on search criteria*/
            return employeeProjectViewRepository.findAll(employeeExample, PageRequest.of(employeeRequestDTO.getCurrentPageNumber(),
                    employeeRequestDTO.getPageSize(), Sort.by(employeeRequestDTO.getSortColumnName()).descending()));
        }
    
    
    だから作ろうEmployeeProjectView オブジェクトをコピーして、UIから受信した検索値を入力します.
    ビルドExampleMatcher 要求された値のいずれかに一致するように
    So let’s create EmployeeProjectView object and copy the user entered search values received from UI to it.
    
    Then build ExampleMatcher to match any one of the requested values
    
    ビルドExample からExampleMatcher
    Example<EmployeeProjectView> employeeExample= Example.of(employeeProjectView, customExampleMatcher);
    
    次に、Example 従業員プロジェクトを探す
    /* Get employees based on search criteria*/
    return employeeProjectViewRepository.findAll(employeeExample, PageRequest.of(employeeRequestDTO.getCurrentPageNumber(),
          employeeRequestDTO.getPageSize(), Sort.by(employeeRequestDTO.getSortColumnName()).descending()));
    

    仕様


    春の医者によると

    JPA Specification uses Criteria API to define and build queries programmatically. By writing a criteria, you define the where clause of a query for a domain class. To support specifications, you can extend your repository interface with the JpaSpecificationExecutor interface


    public interface EmployeeRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor 
    { }
    
    仕様は、容易に結合されることができて、あらゆる必要な組合せのために質問(メソッド)を宣言する必要なしで
    /**
     * Builds and return specification object that filters data based on search string
     *
     * @param employeeRequestDTO Employee Projects Request DTO object
     *
     * @return Specification with Employee Id and Filter Text
     */
    private Specification<EmployeeProjectView> getSpecification(EmployeeRequestDTO employeeRequestDTO)
    {
        //Build Specification with Employee Id and Filter Text
        return (root, criteriaQuery, criteriaBuilder) ->
        {
            criteriaQuery.distinct(true);
            //Predicate for Employee Id
            Predicate predicateForEmployee = criteriaBuilder.equal(root.get("employeeId"), employeeRequestDTO.getEmployeeId());
    
            if (isNotNullOrEmpty(employeeRequestDTO.getFilterText()))
            {
                //Predicate for Employee Projects data
                Predicate predicateForData = criteriaBuilder.or(
                        criteriaBuilder.like(root.get("firstName"), "%" + employeeRequestDTO.getFilterText() + "%"),
                        criteriaBuilder.like(root.get("lastName"), "%" + employeeRequestDTO.getFilterText() + "%"),
                        criteriaBuilder.like(root.get("projectId").as(String.class), "%" + employeeRequestDTO.getFilterText() + "%"),
                        criteriaBuilder.like(root.get("projectName"), "%" + employeeRequestDTO.getFilterText() + "%"),
                        criteriaBuilder.like(root.get("projectBudget").as(String.class), "%" + employeeRequestDTO.getFilterText() + "%"),
                        criteriaBuilder.like(root.get("projectLocation"), "%" + employeeRequestDTO.getFilterText() + "%"));
    
                //Combine both predicates
                return criteriaBuilder.and(predicateForEmployee, predicateForData);
            }
            return criteriaBuilder.and(predicateForEmployee);
        };
    }
    
    我々は、従業員IDとフィルタテキストを使用して述語を構築することができます.述語は、1つの義務的な条件と多くの任意の条件を指定することができます.この場合、従業員IDとそれから列の残りを一致するストリングと一致させる必要があります.
    最初の最初の述語を作成する従業員のIDに一致する
    Predicate predicateForEmployee = criteriaBuilder.equal( root.get("employeeId"), employeeRequestDTO.getEmployeeId());
    
    次に、検索テキストを持つすべての列に一致するように
    //Predicate for Employee Projects data
      Predicate predicateForData = criteriaBuilder.or(
          criteriaBuilder.like(root.get("firstName"), "%" + employeeRequestDTO.getFilterText() + "%"),
          criteriaBuilder.like(root.get("lastName"), "%" + employeeRequestDTO.getFilterText() + "%"),
          criteriaBuilder.like(root.get("projectId").as(String.class), "%" + employeeRequestDTO.getFilterText() + "%"),
          criteriaBuilder.like(root.get("projectName"), "%" + employeeRequestDTO.getFilterText() + "%"),
          criteriaBuilder.like(root.get("projectBudget").as(String.class), "%" + employeeRequestDTO.getFilterText() + "%"),
          criteriaBuilder.like(root.get("projectLocation"), "%" + employeeRequestDTO.getFilterText() + "%"));
    
    それから両方の述語を結合する
    /** Combine both predicates   */
    return criteriaBuilder.and(predicateForEmployee, predicateForData);
    
    を参照getSpecification() メソッドemployeeProjectViewRepository.findAll() 方法
    @Override
    public Page<EmployeeProjectView> findEmployeeProjectsBySpecification(EmployeeRequestDTO employeeRequestDTO)
    {
      return employeeProjectViewRepository.findAll(getSpecification(employeeRequestDTO), PageRequest.of(employeeRequestDTO.getCurrentPageNumber(), employeeRequestDTO.getPageSize(),
          Sort.by(isNotNullOrEmpty(employeeRequestDTO.getSortDirection()) ? Sort.Direction.fromString(employeeRequestDTO.getSortDirection()) : Sort.Direction.DESC, employeeRequestDTO.getSortColumnName())));
    }
    

    クエリ


    マルチカラム検索を実装する最後の方法は@Query 注釈.
    public interface EmployeeProjectViewRepository extends JpaRepository<EmployeeProjectView, Long>,  JpaSpecificationExecutor<EmployeeProjectView>
    {
        @Query(value = "SELECT e FROM EmployeeProjectView as e WHERE e.employeeId=:employeeId and (:inputString is null or e.lastName like %:inputString% ) and " +
                "(:inputString is null or e.firstName like %:inputString%) and (:inputString is null or concat(e.projectId,'') like %:inputString%) and " +
                " (:inputString is null or e.projectName like %:inputString%) and  (:inputString is null or concat(e.projectBudget,'') like %:inputString%) and "+
                " (:inputString is null or e.projectLocation like %:inputString%)"
        )
        Page<EmployeeProjectView> findAllByInputString(Long employeeId, String inputString, Pageable pageable);
    }
    
    
    上記のように、クエリの注釈を使用してemployeeId 従業員IDと検索テキスト(InputString)パラメータをパラメータとして、列の残りを一致させます.時inputString がNULLでない場合、すべてのカラムに対して比較され、結果が結合されます.

    プロジェクトを実行する

  • リポジトリをクローンする
  • まず、Dockerの作成ファイルを使用してMySQLデータベースをオンラインで持参.これは、MySQLデータベースを開始し、Intelに基づいてデータを挿入するだけでなく、データベースを作成します.SQLファイル
  • $ docker-compose  -f src/main/resources/docker-compose.yml up
    
  • Multi ColumnSearchSpringDataJapAppsクラスを実行して、スプリングブートアプリケーションを起動します
  • 角度Webアプリケーションディレクトリに移動する
  • $ cd src/webapp
    
  • すべての依存関係をインストールする
  • $ npm install
    
  • 角度アプリケーションを開始する
  • $ ng serve --watch
    
  • 移動するhttp://localhost:4200 ブラウザでテーブルを確認します.
    従業員プロジェクト
  • あなたがどんなエラーを得るならば
    The user specified as a definer (‘’@’’) does not exist
    
    MySQLデータベースへの接続
    drop view if exists employee_project_view;
    create view employee_project_view as
    select distinct employee_id,
           first_name,
           last_name,
           project_id,
           name     as project_name,
           location as project_location,
           budget   as project_budget
    from employee,
         employee_project,
         project
    where employee.id = employee_project.employee_id
      and employee_project.project_id = project.id;
    
    今、我々はすべてを使用して、どのアプローチを見てきた?これが私の推薦です
  • 正規のJPAメソッドを解決できない簡単なクエリについては
  • 必須のマッチングとオプションのマッチングを必要とする他の任意の要件については
  • 必須のマッチングと任意のマッチングを必要とする要件については、この記事に示すように、仕様(推奨)またはクエリを使用します.
  • コードアップロードGithub 参考に