참고:
- 이 자습서는 Oracle에서 제공하는 무료 실습 환경에서 사용할 수 있습니다.
- Oracle Cloud Infrastructure 인증서, 테넌시 및 구획에 대한 예제 값이 사용됩니다. 랩을 완료한 후에는 이러한 값을 클라우드 환경과 관련된 값으로 대체하십시오.
TASK 4: 데이터베이스 질의 구현 및 Micronaut 애플리케이션 구축
이 실습에서는 데이터베이스 쿼리를 구현하고 Oracle Autonomous Database에 연결되는 Micronaut 애플리케이션을 로컬로 구축합니다.
예상 시간: 30분
태스크 콘텐츠
이 작업에서 다음을 수행합니다.
- Oracle Database 테이블에 매핑되는 Micronaut 데이터 엔티티 생성
- Micronaut 데이터 저장소를 정의하여 쿼리 구현
- Micronaut 컨트롤러를 REST 엔드포인트로 노출
- 애플리케이션 시작 시 데이터 채우기
- Micronaut 애플리케이션에 대한 통합 테스트 실행
1단계: Oracle Database 테이블에 매핑되는 Micronaut 데이터 엔티티 생성
이전 작업에서 OWNER
이라는 테이블을 생성하는 SQL 스크립트와 PET
라는 테이블이 한 번씩 실행되었습니다. 다음으로 데이터베이스 테이블에서 데이터를 읽는 데 사용할 수 있는 엔티티 클래스를 정의해야 합니다.
-
src/main/java/com/example
패키지에Owner
를 나타낼 엔티티 클래스를 생성합니다.src/main/java/com/example
을 마우스 오른쪽 단추로 눌러 Content 메뉴를 확장하고 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
주석은 매핑된 엔티티를 인스턴스화하는 데 사용될 생성자에서 사용되며 필수 열을 표현하는 데도 사용됩니다. 이 경우age
열이 필요하지 않고setAge
설정자를 사용하여 독립적으로 설정할 수 있는 반면name
열은 필수이며 변경할 수 없습니다. -
src/main/java/com/example
아래에pet
테이블을 모델링하기 위해Pet
엔티티를 나타낼Pet.java
파일을 생성합니다.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 쿼리를 자동으로 구현하는 인터페이스 정의를 지원합니다. 이 섹션에서는 이 Micronaut 데이터 기능을 활용할 수 있습니다.
-
src/main/java/com/example
아래에repositories
라는 별도의 폴더를 생성합니다. -
OwnerRepository.java
파일에서ORACLE
dialect를 사용하여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
인터페이스는 두 개의 일반 인수 유형을 사용합니다. 첫번째 유형은 엔티티 유형(이 경우Owner
)이고 두번째 유형은 ID 유형(이 경우Long
)입니다.CrudRepository
인터페이스는 데이터베이스에서 컴파일 시 자동으로 계산되는 적절한 SQL 삽입, 선택, 갱신 및 삭제를 사용하여 CRUD(엔티티 생성, 읽기, 업데이트 및 삭제)를 수행하는 방법을 정의합니다. 자세한 내용은 CrudRepository의 Javadoc를 참조하십시오.인터페이스 내에서 JDBC 질의를 수행하고 정확한 트랜잭션 의미(질의의 경우 읽기 전용 트랜잭션) 정의, 질의를 실행하고 결과 집합을 이전에 정의한
Owner
엔티티 클래스에 매핑하는 등 모든 복잡한 세부 정보를 자동으로 처리하는 메소드를 정의할 수 있습니다.위에 정의된
findByName
메소드는 컴파일 시에SELECT ID, NAME, AGE FROM OWNER WHERE NAME = ?
와 같은 query를 자동으로 생성합니다.질의 방법 및 정의할 수 있는 질의 유형에 대한 자세한 내용은 Micronaut 데이터 설명서의 질의 방법 설명서를 참조하십시오.
-
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는 특정 query에 필요한 열만 선택하여 보다 최적화된 query를 생성할 수 있는 간단한 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 데이터의 다른 중요한 기능을 사용하므로 흥미롭습니다. 이 도구를 사용하면 데이터베이스 조인을 통해 필요한 데이터를 정확히 검색하여 훨씬 효율적으로 query할 수 있도록 조인 경로를 지정할 수 있습니다.
데이터 저장소를 적절히 사용하면 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); } }
컨트롤러 클래스는 컨트롤러가 매핑되는 루트 URI(이 경우
/owners
)를 정의하는 데 사용할 수 있는@Controller
주석으로 정의됩니다.Micronaut에 컨트롤러가 데이터베이스와 I/O 통신을 수행하므로 I/O 스레드 풀에서 실행해야 함을 알리는 데 사용되는
@ExecuteOn
주석을 확인합니다.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
메소드는 StartupEvent
를 수신하는 인수를 사용하여 @EventListener
로 주석 처리됩니다. 이 이벤트는 응용 프로그램이 실행되고 나면 호출되며, 응용 프로그램이 이를 수행할 준비가 되면 데이터를 지속하는 데 사용할 수 있습니다.
나머지 예제는 CrudRepository 인터페이스의 saveAll 메소드를 사용하여 일부 엔티티를 저장하는 방법을 보여줍니다.
메소드 실행 중에 예외가 발생하면 롤백된 JDBC 트랜잭션에서 Micronaut 데이터가 init
메소드 실행을 래핑하도록 하는 메소드에서 javax.transaction.Transactional
가 선언됩니다.
5단계: Micronaut 애플리케이션에 대한 통합 테스트 실행
응용 프로그램이 응용 프로그램을 성공적으로 시작할 수 있는지 여부를 확인하는 단일 테스트로 이미 설정되었습니다. 따라서 이전 섹션에 정의된 init
메소드의 논리를 테스트합니다.
-
위쪽 탐색에서 Terminal, New Terminal으로 이동합니다.
-
test
목표를 실행하여 테스트를 실행합니다../mvnw test
또는 Gradle을 사용하는 경우
test
작업을 실행합니다../gradlew test
테스트 실행이 완료되면 BUILD SUCCESS 메시지가 표시됩니다.
이제 다음 작업 진행할 수 있습니다.