0
0

Spring BootでDAOからデータベースにアクセスしてみた-その1(DAOとアノテーション)

Posted at

背景・目的

過去に、Spring Bootについて下記の記事にまとめました。今回は、EntityManagerによるデータベースアクセスを試します。

まとめ

下記に特徴をまとめます

特徴 説明
DAO Database Access Objectの略

永続化のメカニズムを提供する抽象化インターフェイス

この分離により、単一責任の原則をサポートする
JPQL Jakarta Persistence Query Languageの略

リレーショナルデータベースに格納されたエンティティに対してクエリを実行するために使用される
クエリアノテーション メンテナンスの手間が軽減
NamedQueryアノテーション クエリアノテーションの一種

エンティティで実装
Queryアノテーション クエリアノテーションの一種

DAOやリポジトリで実装

概要

Spring Bootでは、JPAの一部である、リポジトリをつかって簡単なデータアクセスができます。
今回は、リポジトリ以外のJPAの機能について、整理します。

DAO

Data access objectを基に整理します。

  • Database Access Object(以降、DAOという)は、データベースや、その他の永続化のメカニズムを提供する抽象化インフェイス
  • アプリケーションの呼び出しを永続化レイヤーとのマッピングにより、詳細なデータ操作を隠蔽する
  • この分離により、単一責任の原則をサポートする
    • これにより、ドメイン固有のオブジェクトとデータ型 (DAO のパブリック インターフェイス) の観点からアプリケーションが必要とするデータ アクセスが、特定のDBMSでこれらのニーズを満たす方法(DAO の実装) から分離される

JPQL

Jakarta Persistence Query Languageを基に整理します。

  • Jakarta Persistence Query Languageの略
    • 以前は、Java Persistence Query Languageのようです
  • リレーショナルデータベースに格納されたエンティティに対してクエリを実行するために使用される
  • そのクエリは構文がSQLクエリに似ている
  • データベーステーブルを直接操作するのではなく、JPAエンティティオブジェクトに対して操作する
  • JPQLをJPAが受け付けて、SQLに変換し、データベースにクエリを実行する

クエリアノテーション

Annotation-based Configurationを基に整理します。

  • クエリをリテラルとしてDAOクラス内に文字列リテラルとして埋め込まれているのは、メンテナンスの観点から好ましくない
  • アノテーションベースの構成には、別の構成ファイルを編集する必要がないため、メンテナンスの手間が軽減されるという利点がある

NamedQueryアノテーション

  • クエリアノテーションには、クエリに名前をつけて利用できる「名前付きクエリ」を作成できる

Queryアノテーション

  • NamedQueryでは、エンティティクラスにあらかじめ定義しておくが、エンティティに寄せると、メンテナンス性が悪い

実践

前提

Spring Bootでバリデーションを試してみたで使用していた環境を再利用します。

DAOを用意する

インタフェイスの実装

  1. コントローラーと同一パッケージにPersonDAOを用意します
  2. 下記のコードを追加します
    package com.example.data.access;
    
    import java.io.Serializable; // Add this import statement
    
    import java.util.List;
    
    public interface PersonDAO<T> extends Serializable {
        public List<T> getAll();
    }
    

DAOの実装

  1. DAOインタフェイスと同一パッケージにPersonDAOPersonImplを用意します
  2. 下記のコードを追記します
    package com.example.data.access;
    
    import java.util.List; // Import the correct List class
    
    import org.springframework.stereotype.Repository;
    
    import jakarta.persistence.EntityManager;
    import jakarta.persistence.PersistenceContext;
    import jakarta.persistence.Query; // Import the Query class
    
    @Repository
    public class PersonDAOPersonImpl implements PersonDAO<Person> {
    
        private static final long serialVersionUID = 1L;
    
        @PersistenceContext
        private EntityManager entityManager;
    
        public PersonDAOPersonImpl() {
            super();
        }
    
        @Override
        public List<Person> getAll() {
            Query query = entityManager.createQuery("FROM Person p");
            @SuppressWarnings("unchecked")
            List<Person> list = query.getResultList(); // Remove the unnecessary cast
            entityManager.close();
            return list;
        }
    
    }
    
    • EntityManagerクラス
      • エンティティを操作するための機能を有する
      • エンティティを扱う場合は、用意する
    • @PersistenceContext
      • EntityManagerにつけられたアノテーション
      • EntityManagerのBeanを取得し、フィールドに設定するためにある
      • EntityManagerは、Spring Bootアプリケーションを起動時に自動手でBeanとしてインスタンスが登録されている。これを@PersisutentContextにより、フィールドに割り当てる
      • このアノテーションを利用するのが、Spring Data JPAの基本
    • createQueryメソッド
      • Queryオブジェクトは、SQLでデータを問い合わせるためのクエリに相当する
      • JPAでは、JPQLと呼ばれるクエリがある。SQLに似た形の問い合わせ言語
      • createQueryにより、JPQLによるクエリを指定して呼び出している
    • getResultListメソッド
      • ListインスタンスでJPQLの結果が返される
      • from Personとしているので、ListにはPersonインスタンスがまとめられている
    • @SuppressWarningsアノテーション
      • uncheckedを引数に渡すことで、Listで得られているかチェックをおこなうか
      • ビルド時に警告を出すかをコントロールする

コントローラー

  1. Spring Bootでバリデーションを試してみたで使用していたWebControlloerに下記のコードを追加します
      @Autowired
      PersonDAOPersonImpl dao;
    
      @RequestMapping(value = "/find", method=RequestMethod.GET)
      public ModelAndView index(ModelAndView mav) {
        mav.setViewName("find");
        mav.addObject("title","Find Page");
        mav.addObject("message","Please enter a name.");
        
        List<Person> list = dao.getAll();
        mav.addObject("people", list);
        return mav;
      }
    
    • DAOのAutowired
      • Autowiredは、アプリケーションが用意したインスタンスを自動的に割り当てるアノテーション
    • findメソッドの処理
      • PersonDAOPersonImplからgetAllでListを取得している
      • DAOにアクセスをまとめておくことで、コントローラーでは呼び出すだけで済む

ビューテンプレート

  1. templateにfind.htmlを追加します
  2. 下記のコードを追記します
    <!DOCTYPE html>
    <html> 
    <head>
        <title>top page</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css">
    </head>
    <body class="container">
      <h1 class="display-4 mb-4" th:text="${title}"></h1>
      <p th:text="${message}"></p>
      <form method="post" action="/find">
        <div class="input-group">
          <input type="text" class="form-control me-1" name="find_str" th:value="${value}" />
          <span class="input-group-btn">
            <input type="submit" class="btn btn-primary px-4" value="Click" />
          </span>
        </div>
    
      </form>
    
      <table class="table">
        <thread>
          <tr>
            <th>firstName</th>
            <th>lastName</th>
            <th>Phone</th>
          </tr>
        </thread>
        <tbody>
          <tr th:each="person : ${people}">
            <td th:text="${person.firstName}"></td>
            <td th:text="${person.lastName}"></td>
            <td th:text="${person.phone}"></td>
          </tr>
        </tbody>
      </table>
    </body>
    </html>
    

動作確認

  1. ビルドしSpring Bootを再実行します
    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

現時点では、Click後のアクションを定義していないので、ここまでとします。

ID検索できるようにする

DAOインタフェイス

  1. PersonDAOに、下記のメソッドを追加します
    • findById
      • ID番号を引数に指定してエンティティを検索し返す
    • findByName
      • 名前から検索する
    package com.example.data.rest_db;
    
    import java.io.Serializable; // Add this import statement
    
    import java.util.List;
    
    public interface PersonDAO<T> extends Serializable {
        public List<T> getAll();
    
        public T findById(int id);
    
        public List<T> findByFirstName(String name);
    }
    

PersonDAOPersonImplクラス

  1. PersonDAOPersonImplに、下記のメソッドを実装します
        @Override
        public Person findById(long id) {
            return (Person)entityManager.createQuery("FROM Person p where p.id = :id")
                .setParameter("id", id)
                .getSingleResult();
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public List<Person> findByFirstName(String name) {
            return (List<Person>)entityManager.createQuery("FROM Person p where p.firstName = :name")
                .setParameter("name", name)
                .getResultList();
        }
    
    • findById
      • JPQLのクエリ
      • getSingleResultは、Queryから得られるエンティティを1つだけ取り出す
    • findByName
      • getResultListは、Listを返す

コントローラー

  1. コントローラーに下記のメソッドを追加します
      @RequestMapping(value = "/find", method=RequestMethod.POST)
      public ModelAndView search(HttpServletRequest request, ModelAndView mav){
        mav.setViewName("find");
        String param = request.getParameter("find_str");
        if (param == ""){ 
          mav = new ModelAndView("redirect:/find");
        } else {
          mav.addObject("title","Find result");
          mav.addObject("message","Result for " + param);
          mav.addObject("value", param);
          Person person = dao.findById(Long.parseLong(param));
          Person[] list = new Person[] {person};
          mav.addObject("people", list);
        }
        return mav; // Add this return statement
      }
    
    • HttpServletRequest
      • JSPやServlertで利用されていたもの
      • doGet/doPostで利用していた

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

  3. ID「1」を入力し、Clickします。絞り込みされました
    image.png

名前で検索できるようにする

コントローラー

  1. コントローラーのメソッドを修正します
      @RequestMapping(value = "/find", method=RequestMethod.POST)
      public ModelAndView search(HttpServletRequest request, ModelAndView mav){
        mav.setViewName("find");
        String param = request.getParameter("find_str");
        if (param == ""){ 
          mav = new ModelAndView("redirect:/find");
        } else {
          mav.addObject("title","Find result");
          mav.addObject("message","Result for " + param);
          mav.addObject("value", param);
          // Person person = dao.findById(Long.parseLong(param));
          // Person[] list = new Person[] {person};
          List<Person> list = dao.findByFirstName(param);
          mav.addObject("people", list);
        }
        return mav; // Add this return statement
      }
    

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

  3. firstName「Hanako」を入力し、Clickします。想定通り絞り込めました
    image.png

  4. ID「1」を入力し、Clickします。想定通りヒットしませんでした
    image.png

複数のパラメータを追加

firstName、LastName、Phone、IDで検索できるように、いずれかの条件にヒットしたら返されるようにします。

DAOインタフェイス

  1. 下記のメソッドを追加します
    public List<T> find(String fstr);
    

DAOクラス

  1. 下記のメソッドを追加します
        @SuppressWarnings("unchecked")
        @Override
        public List<Person> find(String fstr){
            List<Person> list = null;
            String qstr = "from Person where id =:fid OR firstName like :fname OR lastName like :lname OR phone like :ph";
            Long fid = 0L;
            try {
                fid = Long.parseLong(fstr);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
            Query query = entityManager.createQuery(qstr)
                .setParameter("fid", fid)
                .setParameter("fname", "%"+fstr+"%")
                .setParameter("lname", "%"+fstr+"%")
                .setParameter("ph", "%"+fstr+"%");
            list = query.getResultList();
            return list;
        }
    
    

コントローラーの修正

  1. 上記のDAOインタフェイスを利用するように修正します
      @RequestMapping(value = "/find", method=RequestMethod.POST)
      public ModelAndView search(HttpServletRequest request, ModelAndView mav){
        mav.setViewName("find");
        String param = request.getParameter("find_str");
        if (param == ""){ 
          mav = new ModelAndView("redirect:/find");
        } else {
          mav.addObject("title","Find result");
          mav.addObject("message","Result for " + param);
          mav.addObject("value", param);
          // Person person = dao.findById(Long.parseLong(param));
          // Person[] list = new Person[] {person};
          // List<Person> list = dao.findByFirstName(param);
          List<Person> list = dao.find(param);
          mav.addObject("people", list);
        }
        return mav; // Add this return statement
      }
    

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

  3. IDを想定して、「3」を入力し「Click」をクリックします
    image.png

  4. firstNameを想定して「Taro」を入力し、「Click」をクリックします
    image.png

  5. lastNameを想定して「Yamada」を入力し、「Click」をクリックします
    image.png

  6. Phoneを想定して「0003」を入力し、「Click」をクリックします
    image.png

クエリアノテーション

@NamedQueryというアノテーションを使えます。

エンティティの修正

  1. Personクラスに、下記を追加します
    • クラス上に@NamedQueryアノテーションを追加します
    @Entity
    @Table(name = "people")
    @NamedQuery(
      name = "find",
      query = "from Person where id =:fid OR firstName like :fname OR lastName like :lname OR phone like :ph"
    )
    public class Person {
    
  2. 複数を追加する場合には、下記のように変更します
    @Entity
    @Table(name = "people")
    @NamedQueries(
      @NamedQuery(
        name = "find",
        query = "from Person where id =:fid OR firstName like :fname OR lastName like :lname OR phone like :ph"
      )
    )
    public class Person {
    

DAOの修正

上記のエンティティに用意した、NamedQueryを利用します。

  1. DAOのfindメソッドを下記のように修正します
    @SuppressWarnings("unchecked")
    @Override
    public List<Person> find(String fstr){
        List<Person> list = null;
        
        Long fid = 0L;
        try {
            fid = Long.parseLong(fstr);
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        Query query = entityManager
                    .createNamedQuery("find")
                    .setParameter("fid", fid)
                    .setParameter("fname", "%"+fstr+"%")
                    .setParameter("lname", "%"+fstr+"%")
                    .setParameter("ph", "%"+fstr+"%");
        list = query.getResultList();
        return list;
    }
    

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

  3. lastNameを想定して、「Yamada」を入力し、「Click」します。想定通りでした
    image.png

リポジトリやアノテーションを実装する

リポジトリ

  1. PersonRepositoryインターフェイスに新たにfindメソッドを追加します
    package com.example.data.rest_db.repositories;
    
    import com.example.data.rest_db.Person;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.stereotype.Repository;
    
    import java.util.List; // Add this import statement
    import java.util.Optional; // Add this import statement
    
    @Repository
    public interface PersonRepository extends JpaRepository<Person, Long> {
    
      @Query("SELECT p FROM Person p ORDER BY p.id")
      List<Person> find();
    
      public Optional<Person> findById (Long id);
    }
    

コントローラーの修正

  1. WebControllerクラスの、indexメソッドを下記のように修正します
      @RequestMapping("/")
      public ModelAndView index(
        @ModelAttribute("formModel") Person person,
        ModelAndView mav) {
    
        mav.setViewName("index");
        mav.addObject("title","Test page");
        mav.addObject("message","This is JPA sample content.");
        // List<Person> list = repository.findAll();
        List<Person> list = repository.findAllOrderByName();
        mav.addObject("people", list);
    
        return mav;
      }
    

動作確認

  1. ビルドしSpring Bootを再実行します
    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

リポジトリやアノテーションを実装する(パラメータ渡し)

リポジトリ

  1. PersonRepositoryにfindByNameメソッドを追加します
    package com.example.data.rest_db.repositories;
    
    import com.example.data.rest_db.Person;
    import org.springframework.data.jpa.repository.JpaRepository;
    import org.springframework.data.jpa.repository.Query;
    import org.springframework.stereotype.Repository;
    import org.springframework.data.repository.query.Param; // Add this import statement
    
    import java.util.List; // Add this import statement
    import java.util.Optional; // Add this import statement
    
    @Repository
    public interface PersonRepository extends JpaRepository<Person, Long> {
    
      @Query("SELECT p FROM Person p ORDER BY p.id")
      List<Person> findAllOrderByName();
    
      @Query("FROM Person WHERE firstName LIKE :sname OR lastName LIKE :sname ")
      public List<Person> findByName(@Param("sname") String sname);
      
      public Optional<Person> findById (Long id);
    }
    

コントローラー

  1. searchメソッドを下記のように修正し、作成した
      @RequestMapping(value = "/find", method=RequestMethod.POST)
      public ModelAndView search(HttpServletRequest request, ModelAndView mav){
        mav.setViewName("find");
        String param = request.getParameter("find_str");
        if (param == ""){ 
          mav = new ModelAndView("redirect:/find");
        } else {
          mav.addObject("title","Find result");
          mav.addObject("message","Result for " + param);
          mav.addObject("value", param);
          // Person person = dao.findById(Long.parseLong(param));
          // Person[] list = new Person[] {person};
          // List<Person> list = dao.findByFirstName(param);
          // List<Person> list = dao.find(param);
          List<Person> list = repository.findByName(param);
          mav.addObject("people", list);
        }
        return mav; // Add this return statement
      }
    

動作確認

  1. ビルドしSpring Bootを再実行します

    $ mvn package
    $ java -jar target/rest-db-0.0.1-SNAPSHOT.jar
    
  2. localhost:8080/findにアクセスします。見えました
    image.png

  3. lastNameを想定して「Yamada」を入力し、「Click」します
    image.png

  4. firstNameを想定して「Taro」を入力し、「Click」をクリックします
    image.png

考察

今回、DAOを使用してデータベースにアクセスしました。また、アノテーションを使用してあらかじめクエリを定義しアクセスすることでメンテナンス性を向上させるテクニックを試しました。
今後も、他の機能についても継続して、試してみます。

参考

Spring Boot 3 プログラミング入門

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0