이전 글 |
2023.09.03 - [Study/Spring Boot] - 3. REST API |
4.1 DB엑세스를 위한 자동 설정 프라이밍
사용자 맞춤 기능 → 사용 패턴에 따라 여러 속성값을 제공, 하나 이상의 맞춤형 빈을 제공하는 기능 등
4.2.1 DB 의존성 추가하기
스프링 부트 애플리케이션에서 DB에 액세스 하기 위해 필요한 것
• 실행 중인 DB - 접속 가능한 DB이거나 개발하는 애플리케이션의 내장 DB • 프로그램 상에서 DB 액세스를 가능하게 해주는 DB 드라이버 • 원하는 DB에 액세스하기 위한 '스프링 데이터' 모듈 |
영속성 DB (Persistance database) → 빌드 파일에 의존성, 기능 추가하기
이번 실습에서 사용할 DB → H2 DB (JPA 호환 DB)
1. 애플리케이션이 H2 DB와 상호작용 하도록 pom.xml의 <dependencies>에 의존성 추가하기
(+) <scope>runtime</scope> : H2가 런타임과 테스트 클래스 경로에 존재하지만 컴파일 클래스 경로에는 존재하지 않게 됨
→ 컴파일 시 필요 없는 라이브러리에는 scope를 runtime으로 설정하는 것이 좋음
4.2.2 코드 추가하기
@Entity 사용하기
Coffee 클래스에 javax.persistence의 @Entity 어노테이션 추가
기존 id 멤버 변수를 DB 테이블의 ID 필드로 표시하기 위해 @Id 어노테이션 추가
JPA를 사용해 DB에 데이터를 생성할 때는 기본 생성자가 필요
(+) @Entity 사용하려면 pom.xml파일에 아래 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
저장소 만들기
자바 애플리케이션 실행을 위해 서버를 준비하는 경우
-PersistenceUnit, EntityManagerFactory, EntityManager API(데이터 소스)와 관련된 추가 단계를 수행해야 함
스프링 데이터
- 위와 같은 반복 작업을 해결하기 위해 Repository 개념을 도입
- Repository: 다양한 DB를 위한 추상화 인터페이스, 아래 타입의 단순한 플레이스홀더
- DB에 저장된 객체
- 객체의 고유 ID와 기본키 필드
*플레이스홀더: 사용자가 어떤 정보를 입력해야 하고 어떤 액션을 취해야 하는지 입력 필드에 표시되는 메시지
CrudRepository
JpaRepository
애플리케이션에서 저장소 사용하려면 먼저 스프링 데이터 Repository 인터페이스를 상속할 인터페이스 정의하기
//Repository 인터페이스를 상속할 인터페이스 정의
interface CoffeeRepository extends CrudRepository<Coffee, String>{} //Coffee 객체, 고유 ID를 담을 String 타입
스프링 부트의 자동 설정은 클래스 경로(이 경우 H2)의 DB 드라이버, 애플리케이션에 정의된 저장소 인터페이스, JPA 엔티티인 Coffee 클래스의 정의를 고려해 사용자를 대신해서 DB 프록시 빈을 생성함
*DB 프록시 빈(Proxy bean)?
Spring JPA Data Repository 기능 알아보기
RestApiDemoController에 CoffeeRepository를 autowire/주입해서
외부 API로 요청이 들어오면 컨트롤러가 저장소에 접근하게 하기
1. 멤버 변수 선언
2. 생성자에 CoffeeRdpository 매개변수 추가
coffees 변수 사용했던 부분 다 변경해주기
findAll() | |
findById(id) | 원하는 id를 찾아줌, Optional 타입 반환 |
save(coffee) | |
deleteById(id) |
@RestController
@RequestMapping("/coffees")
class RestApiDemoController {
// private List<Coffee> coffees = new ArrayList<>();
private final CoffeeRepository coffeeRepository; //멤버 변수 선언
public RestApiDemoController(CoffeeRepository coffeeRepository) {//생성자에 매개변수 추가
this.coffeeRepository = coffeeRepository;
this.coffeeRepository.saveAll(Arrays.asList(
new Coffee("Cafe Cereza"),
new Coffee("Cafe Ganador"),
new Coffee("Cafe Lareno"),
new Coffee("Cafe Tres Pontas")
));
/* coffees.addAll(Arrays.asList(
new Coffee("Cafe Cereza"),
new Coffee("Cafe Ganador"),
new Coffee("Cafe Lareno"),
new Coffee("Cafe Tres Pontas")
));*/
}
@GetMapping
Iterable<Coffee> getCoffees() {
return coffeeRepository.findAll();
// return coffees;
}
@GetMapping("/{id}")
Optional<Coffee> getCoffeeById(@PathVariable String id){
return coffeeRepository.findById(id);
/* for(Coffee c : coffees) {
if(c.getId().equals(id)) {
return Optional.of(c);
}
}
return Optional.empty();*/
}
@PostMapping
Coffee postCoffee(@RequestBody Coffee coffee) {
return coffeeRepository.save(coffee);
/* coffees.add(coffee);
return coffee;*/
}
@PutMapping("/{id}")
ResponseEntity<Coffee> putCoffee(@PathVariable String id, @RequestBody Coffee coffee) {
return(!coffeeRepository.existsById(id))
? new ResponseEntity<>(coffeeRepository.save(coffee),HttpStatus.CREATED)
: new ResponseEntity<>(coffeeRepository.save(coffee),HttpStatus.OK);
/*int coffeeIndex = -1;
for(Coffee c : coffees) {
if(c.getId().equals(id)) {
coffeeIndex = coffees.indexOf(c);
coffees.set(coffeeIndex, coffee);
}
}
return (coffeeIndex == -1) ?
new ResponseEntity<>(postCoffee(coffee), HttpStatus.CREATED) : //상태코드 반환 추가
new ResponseEntity<>(coffee, HttpStatus.OK);*/
}
@DeleteMapping("/{id}")
void deleteCoffee(@PathVariable String id) {
coffeeRepository.deleteById(id);
// coffees.removeIf(c->c.getId().equals(id));
}
}
4.3 데이터 저장과 조회 (결과 확인하기)
4.4 추가적으로 다듬기
샘플용 커피 데이터를 자동 생성하는 부분을 별도 컴포넌트로 분리
→ 초기 데이터 생성 기능을 별도의 컴포넌트로 분리해서 언제든지 쉽게 활성화 또는 비활성화 할 수 있게 하는 것이 더 나은 구조
애플리케이션 실행 시 자동으로 코드를 실행하게
- CommandLineRunner, ApplicationRunner 사용, 람다 사용하는 방법 등이 존재함
아래 이유 때문에 여기서는 @Component 클래스, @PostConstruct 메서드 사용하는 편이 좋음
- CommandLineRunner와 ApplicationRunner가 repository 빈을 autowire하면, repository 빈을 Mock 객체로 대체하기 어려우므로 일부 단위 테스트가 제대로 동작하지 않음 - 만약 테스트 내에서 repository 빈을 Mock 객체로 대체해 사용하거나 샘플 데이터를 생성하지 않고 애플리케이션을 실행하면, @Component 어노테이션을 주석 처리해서 데이터를 추가하는 빈을 손쉽게 비활성화 함 |
@Component
class DataLoader{
private final CoffeeRepository coffeeRepository;
public DataLoader(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
}
@PostConstruct
private void loadData() {
this.coffeeRepository.saveAll(Arrays.asList(
new Coffee("Cafe Cereza"),
new Coffee("Cafe Ganador"),
new Coffee("Cafe Lareno"),
new Coffee("Cafe Tres Pontas")
));
}
}
'Study > Spring Boot' 카테고리의 다른 글
6. 데이터 파고들기 (2) | 2023.12.14 |
---|---|
5. 애플리케이션 설정과 검사 (0) | 2023.09.17 |
3. REST API (0) | 2023.09.03 |