4.2. インフラストラクチャ層の実装

Caution

本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。

インフラストラクチャ層では、RepositoryImplの実装を行う。

RepositoryImplは、Repositoryインタフェースで定義したメソッドの実装を行う。

4.2.1. RepositoryImplの実装

以下に、JPAとMyBatis2系を使って、リレーショナルデータベース用のRepositoryを作成する方法を紹介する。

4.2.1.1. JPAを使ってRepositoryを実装

リレーショナルデータベースとの永続APIとして、JPAを使う場合、Spring Data JPAのorg.springframework.data.jpa.repository.JpaRepositoryを使用すると、非常に簡単にRepositoryを作成することが出来る。
Spring Data JPAの使用方法の詳細は、データベースアクセス(JPA編)を参照されたい。
Spring Data JPAを使った場合、基本的なCRUD操作は、JpaRepositoryを継承したインタフェースを作成するだけでよい。つまり、基本的には、RepositoryImplは不要である。
ただし、動的なクエリ(JPQL)を発行する必要がある場合は、RepositoryImplが必要となる。
Spring Data JPA使用時のRepositoryImplの実装については、データベースアクセス(JPA編)を参照されたい。
  • TodoRepository.java
   public interface TodoRepository extends JpaRepository<Todo, String> { // (1)
       // ...
   }
項番 説明
(1)
JpaRepositoryを継承したインタフェースを定義するだけで、Todoエンティティに対する基本的なCRUD操作を実装なしで実現できる。
JpaRepositoryから提供されていない操作を追加する場合について説明する。
Spring Data JPAを使った場合、静的なクエリであればインタフェースにメソッドを追加し、そのメソッドが呼び出された時に実行するクエリ(JPQL)をアノテーションで指定すればよい。
  • TodoRepository.java
 public interface TodoRepository extends JpaRepository<Todo, String> {
     @Query("SELECT COUNT(t) FROM Todo t WHERE finished = :finished") // (1)
     long countByFinished(@Param("finished") boolean finished);
     // ...
 }
項番 説明
(1)
@Queryアノテーションで、クエリ(JPQL)を指定する。

4.2.1.2. MyBatis2系を使ってRepositoryを実装

リレーショナルデータベースとの永続APIとしてMyBatis2系を使う場合、RepositoryImplは、以下のようになる。
MyBatis2の使用方法の詳細は、データベースアクセス(Mybatis2編)を参照されたい。
なお、本ガイドラインではMyBatisを直接使うのではなく、MyBatisのAPIをラップしているTERASOLUNA DAOを使うことを前提としている。
MyBatisを使う場合、Repositoryインタフェースは、必要なメソッドの定義のみ行えばよい。
もちろん、Spring Dataから提供されているCrudRepositoryや、PagingAndSortingRepositoryを使ってもよいが、すべてのメソッドを使うケースは稀なので、余計な実装が必要になってしまう。
MyBatisを使う場合、Repositoryインタフェースの定義に加え、RepositoryImplの実装と、SQL定義ファイルの実装が必要となる。
下記に、以下2点を目的とした、JpaRepositoryの親インタフェースであるPagingAndSortingRepositoryを実装例を示す。
  1. 汎用的なCRUD操作をMyBatisで実装する際のサンプルの提示
  2. Spring Data JPAの仕組みを使ってRepositoryを実装した時との実装比較
  • TodoRepository.java
  public interface TodoRepository extends PagingAndSortingRepository<Todo, String> { // (1)
      long countByFinished(boolean finished);
      // ...
  }
項番 説明
(1)
Spring Dataより提供されているorg.springframework.data.repository.PagingAndSortingRepository(CrudRepositoryの子インタフェース)を継承することで、 Repositoryインタフェースとして必要な、基本的なメソッドの定義が行われる。MyBatisの場合、インタフェースの定義に加えて、RepositoryImplの実装も必要である。
  • TodoRepositoryImpl.java
   @Repository // (1)
   @Transactional // (2)
   public class TodoRepositoryImpl implements TodoRepository {
       @Inject
       protected QueryDAO queryDAO; // (3)

       @Inject
       protected UpdateDAO updateDAO; // (4)

       @Override
       @Transactional(readOnly = true) // (5)
       public Todo findOne(String id) { // (6)
           return queryDAO.executeForObject("todo.findOne", todoId, Todo.class);
       }

       @Override
       @Transactional(readOnly = true) // (5)
       public boolean exists(String id) { // (6)
           Long count = queryDAO.executeForObject("todo.exists", todoId,
               Long.class);
           return 0 < count.longValue();
       }

       @Override
       @Transactional(readOnly = true) // (5)
       public Iterable<Todo> findAll() { // (6)
           return findAll((Sort) null);
       }

       @Override
       @Transactional(readOnly = true) // (5)
       public Iterable<Todo> findAll(Iterable<String> ids) { // (6)
           return queryDAO.executeForObjectList("todo.findAll", ids);
       }

       @Override
       @Transactional(readOnly = true) // (5)
       public Iterable<Todo> findAll(Sort sort) { // (7)
           return queryDAO.executeForObjectList("todo.findAllSort", sort);
       }

       @Override
       @Transactional(readOnly = true) // (5)
       Page<Todo> findAll(Pageable pageable) { // (7)
           long count = count();
           List<Todo> todos = new ArrayList<Todo>();
           if(0 < count){
               todos = queryDAO.executeForObjectList("todo.findAllSort",
                   pageable.getSort(),pageable.getOffset(),pageable.getPageSize());
           } else {
               todos = new ArrayList<Todo>();
           }
           Page page = new PageImpl(todos,pageable,count);
           return page;
       }

       @Override
       @Transactional(readOnly = true) // (5)
       public long count() { // (6)
           Long count = queryDAO.executeForObject("todo.count", null, Long.class);
           return count.longValue();
       }

       @Override
       public <S extends Todo> S save(S todo) { // (6)
           if(exists(todo.getTodoId())){
               updateDAO.execute("todo.update", todo);
           } else {
               updateDAO.execute("todo.insert", todo);
           }
           return todo;
       }

       @Override
       public <S extends Todo> Iterable<S> save(Iterable<S> todos) { // (6)
           for(Todo todo : todos){
               save(todo);
           }
           return todos;
       }

       @Override
       public void delete(String id) { // (6)
           updateDAO.execute("todo.delete", id);
       }

       @Override
       public void delete(Todo todo) { // (6)
           delete(todo.getTodoId());
       }

       @Override
       public void delete(Iterable<? extends Todo> todos) { // (6)
           for(Todo todo : todos){
               delete(todo);
           }
       }

       public long countByFinished(boolean finished) { // (8)
           Long count = queryDAO.executeForObject("todo.countByFinished", finished, Long.class);
           return count.longValue();
       }

   }
項番 説明
(1)
クラスアノテーションとして、@Repositoryアノテーションを付与する。アノテーションを付与することで、component-scan対象となり、設定ファイルへのbean定義が不要となる。
(2)
クラスアノテーションとして、@Transactionalアノテーションを付与する。トランザクション境界は、Serviceで制御するが、Repositoryにも付与しておくこと。
(3)
問い合わせ処理を行うためのjp.terasoluna.fw.dao.QueryDAOをインジェクションする。
(4)
更新処理を行うためのjp.terasoluna.fw.dao.UpdateDAOをインジェクションする。
(5)
問い合わせ系のメソッドには、@Transactional(readOnly = true)を付与する。
(6)
CrudRepositoryで定義されているメソッドを実装している。
(7)
PagingAndSortingRepositoryで定義されているメソッドを実装している。
(8)
TodoRepositoryで追加したメソッドを実装している。
  • sqlMap.xml
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE sqlMap
             PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
             "http://ibatis.apache.org/dtd/sql-map-2.dtd">
 <sqlMap namespace="todo"> <!-- (1) -->

     <resultMap id="todo" class="todo.domain.model.Todo"> <!-- (2) -->
         <result property="todoId" column="todo_id" />
         <result property="todoTitle" column="todo_title" />
         <result property="finished" column="finished" />
         <result property="createdAt" column="created_at" />
     </resultMap>

     <!-- (3) -->
     <select id="findOne" parameterClass="java.lang.String" resultMap="todo">
         <!-- ... -->
     </select>

     <select id="exists" parameterClass="java.lang.String" resultClass="java.lang.Long">
         <!-- ... -->
     </select>

     <select id="findAll" resultMap="todo">
         <!-- ... -->
     </select>

     <select id="findAllSort" parameterClass="org.springframework.data.domain.Sort"
             resultMap="todo">
         <!-- ... -->
     </select>

     <select id="count" resultClass="java.lang.Long">
         <!-- ... -->
     </select>

     <insert id="insert" parameterClass="todo.domain.model.Todo">
         <!-- ... -->
     </insert>

     <update id="update" parameterClass="todo.domain.model.Todo">
         <!-- ... -->
     </update>

     <delete id="delete" parameterClass="todo.domain.model.Todo">
         <!-- ... -->
     </delete>

     <select id="countByFinished" parameterClass="java.lang.Boolean" resultClass="java.lang.Long">
         <!-- ... -->
     </select>

 </sqlMap>
項番 説明
(1)
namespaceを指定する。Entityを一意に特定できる名前を付与する。
(2)
Entityの型の指定とフィールドとカラムのマッピングを行う。
(3)
SQLID毎にSQLを実装する。

4.2.1.3. RestTemplateを使って外部システムと連携するRepositoryを実装

Todo

TBD

次版以降で詳細化する予定である。