注:
- Oracle 提供的免费实验室环境中提供了本教程。
- 它使用 Oracle Cloud Infrastructure 身份证明、租户和区间示例值。完成实验室后,请使用特定于您的云环境的这些值替换这些值。
TASK 4:实施数据库查询并构建 Micronaut 应用程序
在此实验室中,您将实施数据库查询并在本地构建连接到 Oracle Autonomous Database 的 Micronaut 应用。
估计时间:30 分钟
任务内容
在本任务中,您将执行以下操作:
- 创建映射到 Oracle Database 表的 Micronaut 数据实体
- 定义 Micronaut 数据存储库以实施查询
- 将 Micronaut 控制器公开为 REST 端点
- 在应用程序启动时填充数据
- 为 Micronaut 应用程序运行集成测试
步骤 1:创建映射到 Oracle Database 表的 Micronaut 数据实体
在前面的任务中,您添加了 SQL 脚本,该脚本在执行后将创建一个名为 OWNER 的表和一个名为 PET 的表。接下来,您必须定义可用于从数据库表中读取数据的实体类。
-
在
src/main/java/com/example程序包中创建将表示Owner的实体类。右键单击src/main/java/com/example以展开内容菜单,选择“New File(新建文件)”,将其命名为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.java文件,表示Pet实体,以对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 数据支持定义接口的概念,这些接口使用数据存储库模式在编译时自动为您实施 SQL 查询。在此部分中,您将利用此微宇航员数据功能。
-
在
src/main/java/com/example下创建一个名为repositories的单独文件夹。 -
使用名为
OwnerRepository.java的文件中的ORACLE方言定义从CrudRepository扩展且使用@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 个通用参数类型。第一个是实体的类型(在本例中为Owner),第二个是 ID 的类型(在本例中为Long)。CrudRepository界面定义了允许您使用编译时为您计算的相应 SQL 插入、选择、更新和删除 (CRUD) 实体在数据库中创建、读取、更新和删除 (CRUD) 实体的方法。有关更多信息,请访问 CrudRepository 的 Javadoc。您可以在界面中定义执行 JDBC 查询的方法并自动处理您的所有复杂详细信息,例如定义正确的事务处理语义(查询的只读事务处理),执行查询并将结果集映射到之前定义的
Owner实体类。上面定义的
findByName方法将在编译时自动生成诸如SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?之类的查询。有关查询方法和可以定义的查询类型的更多信息,请参阅 Micronaut 数据文档中的查询方法的文档。
-
在
OwnerRepository就位的情况下,定义另一个系统信息库,这次使用数据传输对象 (data transfer object, 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,允许您仅选择特定查询需要的列,从而生成更优化的查询。
-
对于在同一位置
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),并将其分配给查询的Pet的Owner字段。findByName方法也很有趣,因为它使用@Join注释的 Micronaut 数据的另一个重要功能。它允许您指定联接路径,以便通过数据库联接准确检索所需的数据,从而提高查询效率。
在部署数据存储库后,继续公开 REST 端点。
步骤 3:将 Micronaut 控制器公开为 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以按名称显示 pets 和 pets 的列表。
步骤 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 数据在 JDBC 事务处理中包装 init 方法的执行,该方法在执行方法期间发生异常时回退。
步骤 5:为 Micronaut 应用程序运行集成测试
已使用单个测试设置应用程序,该测试检查应用程序是否可以成功启动(因此将测试上一节中定义的 init 方法的逻辑)。
-
从顶部导航中,依次转至 Terminal(终端)和 New Terminal(新建终端)。
-
运行
test目标以执行测试:
./mvnw test
或者,如果您使用 Gradle,请运行
test任务:
./gradlew test测试运行结束时应看到 BUILD SUCCESS 消息。
您现在可以继续执行下一个任务。