備註:

TASK 4:實行資料庫查詢並建立微米自動應用程式

在此實驗室中,您將在連線至 Oracle Autonomous Database 的本機導入資料庫查詢和建立 Micronaut 應用程式。

預估時間:30 分鐘

任務內容

在此工作中,您將:

步驟 1:建立對應至 Oracle Database 表格的 Micronaut 資料實體

在先前的工作中,您新增了 SQL 命令檔,此命令檔會建立一個名為 OWNER 的表格,並在執行後稱為 PET 的表格。接下來您必須定義可用於從資料庫表格讀取資料的實體類別。

  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 資料欄來代表主索引鍵,而 @GeneratedValue 將會設定對應來假設在 Autonomous Database 中使用 identity 資料欄。

    @Creator 註解會用於建立對應實體的建構子,而且也用來表示必要的資料欄。在此情況下,需要 name 資料欄且不可變更,而不需要 age 資料欄,而且可以使用 setAge setter 獨立設定。

  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) 註解來定義,指出這是多對一的關係。

    完成之後,即可進入定義儲存區域介面以實行查詢。

步驟 2:定義 Micronaut 資料儲存區域以實行查詢

Micronaut Data 支援定義使用資料儲存區域樣式,在編譯階段自動為您實行 SQL 查詢的介面。在這個區段中,您將利用這個「微米資料」功能。

  1. src/main/java/com/example 下建立名為 repositories 的個別資料夾。

  2. 定義從 CrudRepository 延伸的新儲存庫介面,並使用名為 OwnerRepository.javaORACLE 方言以 @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 介面有兩種一般引數類型。第一個是實體的類型 (在此情況下為 Owner),第二個是 ID 的類型 (在此情況下為 Long)。

    CrudRepository 介面定義方法,可讓您從資料庫中以適當的 SQL 插入、選取、更新及刪除 (CRUD) 實體 (在編譯階段為您計算)。如需詳細資訊,請參閱 CrudRepository 的 Javadoc。

    您可以在介面中定義執行 JDBC 查詢和自動處理所有複雜詳細資訊的方法,例如定義正確的交易語意 (查詢的唯讀交易)、執行查詢,並將結果集對應至您先前定義的 Owner 實體類別。

    上述定義的 findByName 方法會在編譯時自動產生像是 SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ? 的查詢。

    如需有關查詢方法和您可定義的查詢類型的詳細資訊,請參閱「微米人資料」文件中的查詢方法的文件

  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. 在名為 PetRepository.java 的檔案中,為在相同位置 src/main/java/com/example/repositories 中使用 DTO 的 Pet 實體定義名為 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 註解。它可讓您指定結合路徑,以便準確擷取透過資料庫結合所需要的資料,從而提升查詢效率。

將資料儲存區域上線時,可以搬移至顯示 REST 端點。

步驟 3:以 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);
        }
    }
    

    控制器類別是以 @Controller 註解定義,可用來定義控制器對應到的根 URI (在這種情況下為 /owners)。

    請注意 @ExecuteOn 註解,其用於告知 Micronaut 控制器執行與資料庫之間的 I/O 通訊,因此作業應在 I/O 繫線集區上執行

    OwnerController 類別使用 Micronaut 相依性插入來取得您之前定義的 OwnerRepository 儲存區域介面參照,而且可用來實行兩個端點:

    • / - 根端點列出所有擁有者
    • /{name} - 第二個端點使用 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 方法會使用 @EventListener 加註,其中含有可接收 StartupEvent 的引數。此事件會在應用程式啟動後呼叫,而且可以在應用程式準備就緒時用來保存資料。

另一個範例示範使用 CrudRepository 介面的 saveAll 方法儲存一些實體。

請注意,javax.transaction.Transactional 會在方法上宣告,確保 Micronaut Data 在執行方法期間發生異常狀況時倒回的 JDBC 交易中執行 init 方法。

步驟 5:執行 Micronaut 應用程式的整合測試

應用程式已使用單一測試來設定,檢查應用程式是否可順利啟動 (因此會測試上一個區段中定義之 init 方法的邏輯)。

  1. 從頂端瀏覽,依序前往終端機新終端機

  2. 執行 test 目標以執行測試:

    ./mvnw test
    

    在 VS Code 的終端機視窗中查看檔案結構並執行測試

    或者,如果您使用 Gradle,請執行 test 工作:

    ./gradlew test
    

    您應該會在測試執行結束時看到 BUILD SUCCESS 訊息。

您現在可以繼續下一個作業

深入瞭解