備註:
- 本教學課程隨附於 Oracle 提供的免費實驗室環境。
- 此範例使用 Oracle Cloud Infrastructure 證明資料、租用戶及區間的範例值。完成實驗室時,請將這些值替代成雲端環境的特定值。
TASK 4:實行資料庫查詢並建立微米自動應用程式
在此實驗室中,您將在連線至 Oracle Autonomous Database 的本機導入資料庫查詢和建立 Micronaut 應用程式。
預估時間:30 分鐘
任務內容
在此工作中,您將:
- 建立對應至 Oracle Database 表格的 Micronaut Data 實體
- 定義 Micronaut 資料儲存區域以實行查詢
- 以 REST 端點揭露微米自動控制器
- 在應用程式啟動時填入資料
- 對 Micronaut 應用程式執行整合測試
步驟 1:建立對應至 Oracle Database 表格的 Micronaut 資料實體
在先前的工作中,您新增了 SQL 命令檔,此命令檔會建立一個名為 OWNER 的表格,並在執行後稱為 PET 的表格。接下來您必須定義可用於從資料庫表格讀取資料的實體類別。
- 
    建立代表 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資料欄,而且可以使用setAgesetter 獨立設定。
- 
    建立代表 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 查詢的介面。在這個區段中,您將利用這個「微米資料」功能。
- 
    在 src/main/java/com/example下建立名為repositories的個別資料夾。
- 
    定義從 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介面有兩種一般引數類型。第一個是實體的類型 (在此情況下為Owner),第二個是 ID 的類型 (在此情況下為Long)。CrudRepository介面定義方法,可讓您從資料庫中以適當的 SQL 插入、選取、更新及刪除 (CRUD) 實體 (在編譯階段為您計算)。如需詳細資訊,請參閱 CrudRepository 的 Javadoc。您可以在介面中定義執行 JDBC 查詢和自動處理所有複雜詳細資訊的方法,例如定義正確的交易語意 (查詢的唯讀交易)、執行查詢,並將結果集對應至您先前定義的 Owner實體類別。上述定義的 findByName方法會在編譯時自動產生像是SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?的查詢。如需有關查詢方法和您可定義的查詢類型的詳細資訊,請參閱「微米人資料」文件中的查詢方法的文件。 
- 
    使用 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,可讓您只選取特定查詢所需的資料欄,因此會產生更具最佳化的查詢。 
- 
    在名為 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),並將其指派給所查詢之Pet的Owner欄位。findByName方法也有趣,因為它使用「微觀資料」的其他重要功能,即@Join註解。它可讓您指定結合路徑,以便準確擷取透過資料庫結合所需要的資料,從而提升查詢效率。
將資料儲存區域上線時,可以搬移至顯示 REST 端點。
步驟 3:以 REST 端點顯示微速控制器
Micronaut 中的 REST 端點很容易寫入,而且定義為控制器 (依 MVC 樣式而定) 。
- 
    在 src/main/java/com/example/下建立資料夾controllers。
- 
    在名為 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方法的參數。
 
- 
    接著,在 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 方法的邏輯)。
- 
    從頂端瀏覽,依序前往終端機、新終端機。 
- 
    執行 test目標以執行測試: ./mvnw test 或者,如果您使用 Gradle,請執行 test工作: ./gradlew test您應該會在測試執行結束時看到 BUILD SUCCESS 訊息。 
您現在可以繼續下一個作業。