ノート:

TASK 4:データベース問合せの実装とMicronautアプリケーションの作成

この演習では、データベース問合せを実装し、Oracle Autonomous Databaseに接続するMicronautアプリケーションをローカルに構築します。

見積時間: 30分

タスク・コンテンツ

このタスクでは、次のことを行います。

ステップ1: Oracle Database表にマップするMicronautデータ・エンティティの作成

前のタスクで、OWNERという表を作成し、PETという表を実行するSQLスクリプトを追加しました。次に、データベース表からデータを読み取るために使用できるエンティティ・クラスを定義する必要があります。

  1. src/main/java/com/exampleパッケージのOwnerを表すエンティティ・クラスを作成します。src/main/java/com/exampleを右クリックしてコンテンツ・メニューを展開し、「新規ファイル」を選択してOwner.javaという名前を付け、次のコードを貼り付けます。

    package com.example;
    
    import io.micronaut.core.annotation.Creator;
    import io.micronaut.data.annotation.GeneratedValue;
    import io.micronaut.data.annotation.Id;
    import io.micronaut.data.annotation.MappedEntity;
    
    @MappedEntity
    public class Owner {
    
      // The ID of the class uses a generated sequence value
      @Id
      @GeneratedValue
      private Long id;
      private final String name;
      private int age;
    
      // the constructor reads column values by the name of each constructor argument
      @Creator
      public Owner(String name) {
          this.name = name;
      }
    
      // each property of the class maps to a database column
      public int getAge() {
          return age;
      }
    
      public void setAge(int age) {
          this.age = age;
      }
    
      public String getName() {
          return name;
      }
    
      public Long getId() {
          return id;
      }
    
      public void setId(Long id) {
          this.id = id;
      }
    }
    

    @MappedEntity注釈は、エンティティがデータベース表にマップされていることを示すために使用されます。デフォルトでは、これはクラスと同じ名前(この場合はowner)を使用する表になります。

    表の列は、各Javaプロパティによって表されます。前述の例では、主キーを表すためにid列が使用され、Autonomous Databaseでidentity列の使用を想定するように@GeneratedValueによってマッピングが設定されます。

    @Creator注釈は、マップされたエンティティのインスタンス化に使用されるコンストラクタで使用され、必要な列の表現にも使用されます。この場合、name列が必要であり、age列は不要であり、setAgeセッターを使用して独立して設定できます。

  2. Petエンティティを表すPet.javaファイルを作成して、src/main/java/com/exampleの下にpet表をモデル化します。

    package com.example;
    
    import io.micronaut.core.annotation.Creator;
    import io.micronaut.core.annotation.Nullable;
    import io.micronaut.data.annotation.AutoPopulated;
    import io.micronaut.data.annotation.Id;
    import io.micronaut.data.annotation.MappedEntity;
    import io.micronaut.data.annotation.Relation;
    
    import java.util.UUID;
    
    @MappedEntity
    public class Pet {
    
        // This class uses an auto populated UUID for the primary key
        @Id
        @AutoPopulated
        private UUID id;
    
        private final String name;
    
        // A relation is defined between Pet and Owner
        @Relation(Relation.Kind.MANY_TO_ONE)
        private final Owner owner;
    
        private PetType type = PetType.DOG;
    
        // The constructor defines the columns to be read
        @Creator
        public Pet(String name, @Nullable Owner owner) {
            this.name = name;
            this.owner = owner;
        }
    
        public Owner getOwner() {
            return owner;
        }
    
        public String getName() {
            return name;
        }
    
        public UUID getId() {
            return id;
        }
    
        public void setId(UUID id) {
            this.id = id;
        }
    
        public PetType getType() {
            return type;
        }
    
        public void setType(PetType type) {
            this.type = type;
        }
    
        public enum PetType {
            DOG,
            CAT
        }
    }
    

    Petクラスは、自動的に移入されたUUIDを主キーとして使用して、ID生成に対する様々なアプローチを示すことに注意してください。

    PetクラスとOwnerクラスの関係も、@Relation(Relation.Kind.MANY_TO_ONE)注釈を使用して定義されます。これは多対1の関係であることを示します。

    そのためには、問合せを実装するためにリポジトリ・インタフェースの定義に移行します。

ステップ2:クエリーを実装するためのMicronautデータ・リポジトリの定義

Micronaut Dataは、コンパイル時にデータ・リポジトリ・パターンを使用してSQL問合せを自動的に実装するインタフェースを定義する概念をサポートしています。この項では、このMicronautデータ機能を利用します。

  1. src/main/java/com/exampleの下にrepositoriesという別のフォルダを作成します。

  2. CrudRepositoryから拡張され、OwnerRepository.javaというファイルのORACLEダイアレクトを使用して@JdbcRepositoryで注釈が付けられた新しいリポジトリ・インタフェースを定義します。

    package com.example.repositories;
    
    import com.example.Owner;
    import io.micronaut.data.jdbc.annotation.JdbcRepository;
    import io.micronaut.data.model.query.builder.sql.Dialect;
    import io.micronaut.data.repository.CrudRepository;
    
    import java.util.List;
    import java.util.Optional;
    
    // The @JdbcRepository annotation indicates the database dialect
    @JdbcRepository(dialect = Dialect.ORACLE)
    public interface OwnerRepository extends CrudRepository<Owner, Long> {
    
        @Override
        List<Owner> findAll();
    
        // This method will compute at compilation time a query such as
        // SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?
        Optional<Owner> findByName(String name);
    }
    

    CrudRepositoryインタフェースには、2つの一般的な引数タイプがあります。1つ目はエンティティのタイプ(この場合はOwner)で、2つ目はIDのタイプ(この場合はLong)です。

    CrudRepositoryインタフェースは、コンパイル時に計算された適切なSQL挿入、選択、更新および削除を使用して、データベースからエンティティを作成、読取り、更新および削除できるメソッドを定義します。詳細は、Javadoc (CrudRepository)を参照してください。

    JDBC問合せを実行するインタフェース内でメソッドを定義し、正しいトランザクション・セマンティクス(問合せの読取り専用トランザクション)の定義、問合せの実行および結果セットのOwnerエンティティ・クラスへのマッピングなど、すべての複雑な詳細を自動的に処理できます。

    前述のfindByNameメソッドは、コンパイル時にSELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?などの問合せを自動的に生成します。

    問合せ方法および定義できる問合せのタイプの詳細は、Micronautデータ・ドキュメントの問合せ方法のドキュメントを参照してください。

  3. OwnerRepositoryを配置して、別のリポジトリと今回は、データ転送オブジェクト(DTO)を使用して最適化された問合せを実行します。したがって、最初に、src/main/java/com/example/repositoriesの下のNameDTO.javaというファイルにDTOクラスを作成する必要があります。

    package com.example.repositories;
    
    import io.micronaut.core.annotation.Introspected;
    
    @Introspected
    public class NameDTO {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    DTOは単純なPOJOで、特定の問合せに必要な列のみを選択できるため、より最適化された問合せが生成されます。

  4. 同じ場所src/main/java/com/example/repositoriesでDTOを使用するPetエンティティのPetRepository.javaというファイルで、PetRepositoryというリポジトリを定義します。

    package com.example.repositories;
    
    import com.example.Pet;
    import io.micronaut.data.annotation.Join;
    import io.micronaut.data.jdbc.annotation.JdbcRepository;
    import io.micronaut.data.model.query.builder.sql.Dialect;
    import io.micronaut.data.repository.PageableRepository;
    
    import java.util.List;
    import java.util.Optional;
    import java.util.UUID;
    
    @JdbcRepository(dialect = Dialect.ORACLE)
    public interface PetRepository extends PageableRepository<Pet, UUID> {
    
        List<NameDTO> list();
    
        @Join("owner")
        Optional<Pet> findByName(String name);
    }
    

    DTOを返すlistメソッドを書き留めます。このメソッドは、コンパイル時に再度実装されますが、今回は、Pet表のすべての列を取得するのではなく、name列および定義できるその他の列のみを取得します。

    @Join注釈は、結合されたオブジェクト(Owner)を問い合せてインスタンス化し、問合せ対象のPetOwnerフィールドに割り当てます。

    findByNameメソッドは、@Join注釈であるMicronautデータの別の重要な機能を使用するため、興味もあります。これにより、結合パスを指定できるため、データベース結合を介して必要なデータを正確に取得できるため、問合せがより効率的になります。

データ・リポジトリを所定の位置に置いて、RESTエンドポイントを公開します。

ステップ3: MicronautコントローラをRESTエンドポイントとして公開

MicronautのRESTエンドポイントは簡単に記述でき、コントローラ(MVCパターンに従って)として定義されます。

  1. src/main/java/com/example/の下にフォルダcontrollersを作成します。

  2. OwnerController.javaというファイルに新しいOwnerControllerクラスを定義します。

    package com.example.controllers;
    
    import java.util.List;
    import java.util.Optional;
    
    import javax.validation.constraints.NotBlank;
    
    import com.example.Owner;
    import com.example.repositories.OwnerRepository;
    import io.micronaut.http.annotation.Controller;
    import io.micronaut.http.annotation.Get;
    import io.micronaut.scheduling.TaskExecutors;
    import io.micronaut.scheduling.annotation.ExecuteOn;
    
    @Controller("/owners")
    @ExecuteOn(TaskExecutors.IO)
    class OwnerController {
    
        private final OwnerRepository ownerRepository;
    
        OwnerController(OwnerRepository ownerRepository) {
            this.ownerRepository = ownerRepository;
        }
    
        @Get("/")
        List<Owner> all() {
            return ownerRepository.findAll();
        }
    
        @Get("/{name}")
        Optional<Owner> byName(@NotBlank String name) {
            return ownerRepository.findByName(name);
        }
    }
    

    コントローラ・クラスは、コントローラがマップするルートURIを定義するために使用できる@Controller注釈で定義されます(この場合は/owners)。

    @ExecuteOn注釈は、コントローラがデータベースとのI/O通信を実行するため、I/Oスレッド・プールで実行する必要があることをMicronautに伝えるために使用されます。

    OwnerControllerクラスは、Micronaut依存性インジェクションを使用して、前に定義したOwnerRepositoryリポジトリ・インタフェースへの参照を取得し、2つのエンドポイントを実装するために使用されます。

    • / -ルート・エンドポイントにはすべての所有者がリストされます
    • /{name} - 2番目のエンドポイントでは、URIテンプレートを使用して所有者を名前で検索できます。URI変数{name}の値は、byNameメソッドへのパラメータとして提供されます。
  3. 次に、src/main/java/com/example/controllersの下のPetController.javaというファイルに、PetControllerという別のRESTエンドポイントを定義します。

    package com.example.controllers;
    
    import java.util.List;
    import java.util.Optional;
    
    import com.example.repositories.NameDTO;
    import com.example.Pet;
    import com.example.repositories.PetRepository;
    import io.micronaut.http.annotation.Controller;
    import io.micronaut.http.annotation.Get;
    import io.micronaut.scheduling.TaskExecutors;
    import io.micronaut.scheduling.annotation.ExecuteOn;
    
    @ExecuteOn(TaskExecutors.IO)
    @Controller("/pets")
    class PetController {
    
        private final PetRepository petRepository;
    
        PetController(PetRepository petRepository) {
            this.petRepository = petRepository;
        }
    
        @Get("/")
        List<NameDTO> all() {
            return petRepository.list();
        }
    
        @Get("/{name}")
        Optional<Pet> byName(String name) {
            return petRepository.findByName(name);
        }
    }
    

    今回は、PetRepositoryが注入されて、ペットとペットのリストが名前で公開されます。

ステップ4:アプリケーションの起動時のデータの移入

次のステップは、起動時にアプリケーション・データを移入することです。これを行うには、Micronautアプリケーション・イベントを使用します。

src/main/java/com/example/Application.javaクラスを開き、初期ファイル・コンテンツを次のように置き換えます。

package com.example;

import com.example.repositories.OwnerRepository;
import com.example.repositories.PetRepository;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.Micronaut;
import io.micronaut.runtime.event.annotation.EventListener;

import jakarta.inject.Singleton;
import javax.transaction.Transactional;
import java.util.Arrays;

@Singleton
public class Application {
    private final OwnerRepository ownerRepository;
    private final PetRepository petRepository;

    Application(OwnerRepository ownerRepository, PetRepository petRepository) {
        this.ownerRepository = ownerRepository;
        this.petRepository = petRepository;
    }

    public static void main(String[] args) {
        Micronaut.run(Application.class);
    }

    @EventListener
    @Transactional
    void init(StartupEvent event) {
        // clear out any existing data
        petRepository.deleteAll();
        ownerRepository.deleteAll();

        // create data
        Owner fred = new Owner("Fred");
        fred.setAge(45);
        Owner barney = new Owner("Barney");
        barney.setAge(40);
        ownerRepository.saveAll(Arrays.asList(fred, barney));

        Pet dino = new Pet("Dino", fred);
        Pet bp = new Pet("Baby Puss", fred);
        bp.setType(Pet.PetType.CAT);
        Pet hoppy = new Pet("Hoppy", barney);

        petRepository.saveAll(Arrays.asList(dino, bp, hoppy));
    }
}

データを保持できるように、コンストラクタは依存してリポジトリ定義を注入するように変更されています。

最後に、initメソッドには、StartupEventを受け取る引数とともに@EventListener注釈が付けられます。このイベントは、アプリケーションが起動して実行されるとコールされ、アプリケーションの準備が完了したときにデータを永続化するために使用できます。

残りの例は、CrudRepositoryインタフェースのsaveAllメソッドを使用して少数のエンティティを保存する方法を示しています。

メソッドの実行中に例外が発生した場合にロールバックされるJDBCトランザクションで、Micronautデータがinitメソッドの実行をラップすることを確認するメソッド上で、javax.transaction.Transactionalを宣言します。

ステップ5: Micronautアプリケーションの統合テストの実行

アプリケーションは、アプリケーションが正常に起動できるかどうかをチェックする単一のテストですでに設定されています(そのため、前の項で定義されたinitメソッドのロジックをテストします)。

  1. 上部のナビゲーションから、「ターミナル」「新規ターミナル」の順に移動します。

  2. test目標を実行してテストを実行します。

    ./mvnw test
    

    VS Codeのターミナル・ウィンドウでファイル構造を確認し、テストを実行します。

    または、Gradleを使用している場合は、testタスクを実行します:

    ./gradlew test
    

    テスト実行の最後にBUILD SUCCESSメッセージが表示されます。

次のタスクに進むことができます。

さらに学ぶ