Spring Boot 애플리케이션에 DB추가하기

IKNOW·2024년 1월 3일
0

DB를 사용하는 이유

  1. 복원력이 생긴다.

    여기서 말하는 복원력이란 애플리케이션이나 애플리케이션을 실행하는 플랫폼에 장애가 발생했을때, 앱이 재실행된다면 모든 상태를 잃어버리게 된다.

  2. 확장성이 생긴다.

    애플리케이션의 다른 인스턴스를 시작하면 다른 애플리케이션엔 고유한 데이터가 유지된다. 여러 인스턴스간의 데이터가 공유되지 않으므로, 한 인스턴스에서 데이터를 추가, 삭제 업데이트 하는 등의 변경사항이 다른 앱 인스턴스에 액세스하는 유저에게 표시되지 않는다.

DB의존성 추가하기

스프링부트에서 DB에 액세스하려면 몇가지 준비가 필요하다

  • 애플리케이션에 의해 시작되거나 애플리케이션 내에 포함되어 있거나 애플리케이션에서 간단히 액세스 할수 있는 실행중인 데이터 베이스
  • 프로그래밍 방식의 액세스를 강능하게 하는 데이터베이스 드라이버
  • 데이터베이스에 액세스하기 위한 스프링 데이터 모듈

특정 스프링 데이터 모듈에는 데이터베이스 드라이버가 spring Initializr에 단일 종속성으로 포함되어 있다.

JPA를 사용하는 경우에는 Spring Data Jpa 종속성과 데이터 베이스의 특정 드라이버에 대한 종속성을 선택해야한다.

H2

Java로 작성된 데이터베이스로 JPA와 호환되므로 MySql과 같은 다른 JPA 호환 데이터베이스와 동일한 방식으로 애플리케이션을 연결할 수 있다.

또한 인메모리방식과 디스크기반 모드를 사용할 수 있다.

애플리케이션이 H2데이터베이스와 상호 작용할 수 있도록 하기위해선 다음과 같은 종속성을 포함해야 한다.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>

Code

@Entity

H2는 JPA 호환 DB이므로 점들을 연결하기 위해 JPA 어노테이션을 추가해야한다.

Coffee 클래스 자체에 Coffee가 저장 가능한 엔티티임을 선언하는 @Entity 를 추가하고 기존 id멤버 변수에 DB 테이블의 ID 필드로 표시하기 위해 @Id 어노테이션을 추가해야한다.

@Entity
class Coffee {
    @Id
    private final String id;
		private String name;
}

JPA에서는 객체를 생성할 때 사용할 인수가 없는 생성자를 필요로 하므로 NoArgsConstructor를 추가해준다. 그런데 NoArgsConstructor를 사용하기 위해선 모든 멤버변수가 final이 아닌 변수로 선언 되어야 한다.

또한 Coffee클래스에 해당 멤버에 값을 할당할 수 있는 id에 대한 Setter 메서드를 추가해준다.

@Entity
@NoArgsConstructor
class Coffee {
    @Id
    @Setter
    private String id;
		@Setter
    private String name;
}

Repository

이제 Coffee 클래스는 JPA 엔티티로 정의 되었으므로 데이터베이스와 연결을 만들어준다.

스프링 데이터는 repository개념을 도입한다. repository는 db위에 추상화로서 스프링 데이터 에 정의된 인터페이스이다.

repository에는 여러 주제가 있지만 현재는 CrudRepositoryJpaRepository에 대해 학습한다.

JpaRepository는 몇가지 인터페이스를 확장하여 더 광범위한 기능을 통합하는 반면, CrudRepository는 모든 주요 CRUD 기능을 다루며 간단한 애플리케이션에는 충분하다.

Spring Data Repository Interface를 상속받은 특정 인터페이스를 정의함으로써 repository를 활성화 할 수 있다.

interface CoffeeRepository extends CrudRepository<Coffee, String> {}

Springing into action

컨트롤러가 요청을 수신할 때 repository bean에 액세스 할 수 있도록 repository bean을 RestApiDemoController에 AutoWire/injection해야한다.

먼저 CoffeeRepository 인터페이스를 컨트롤러의 멤버변수롤 선언하고 생성자의 매개변수로 추가해준다.

@RestController
@RequestMapping("/coffees")
class RestApiDemoController{
    private final CoffeeRepository coffeeRepository;

    public RestApiDemoController(CoffeeRepository coffeeRepository) {
        this.coffeeRepository = coffeeRepository;
        coffeeRepository.saveAll(List.of(
                new Coffee("Cafe Cereza"),
                new Coffee("Cafe Ganador"),
                new Coffee("Cafe Lareno"),
                new Coffee("Cafe Tres Pontas")
        ));
    }
//before refactoring
@GetMapping("/{id}")
    Optional<Coffee> getCoffeeById(@PathVariable String id) {
        for (Coffee c: coffees) {
            if (c.getId().equals(id)) {
                return Optional.of(c);
            }
        }
        return Optional.empty();
    }

//after refactoring
@GetMapping("/{id}")
    Optional<Coffee> getCoffeeById(@PathVariable String id) {
        return coffeeRepository.findById(id);
    }

getCoffeeById() 메서드를 리팩토링 하면 레포지토리가 제공하는 기능이 코드를 얼마나 간소화 시킬수 있는지 확인 할 수 있다.

coffees에서 일치하는 ID를 손수 검색할 필요가 없으며 CrudRepositoryfindById()메서드가 대신 처리해 준다. 또한 findById()Optional타입을 리턴하기 때문에 메소드의 시그니쳐를 변경할 필요도 없다.

postCoffee()를 컨버팅하는 방법은 굉장히 간단하다.

//before refactoring
@PostMapping
Coffee postCoffee(@RequestBody Coffee coffee) {
    coffees.add(coffee);
    return coffee;
}

//after refactoring
@PostMapping
Coffee postCoffee(@RequestBody Coffee coffee) {
    coffeeRepository.save(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);
}

putCoffee() 메서드의 경우에는 CrudRepository의 간소화 기능을 확인 할 수 있다.

내장된 existsById() 메서드를 사용하여 엔티티가 새로운 Coffee엔티티인지 기 존재하는 Coffee 엔티티인지 확인하고 저장된 Coffee와 적절한 HTTP 상태코드를 반환한다.

이전 포스트에서 언급했듯이 PUT 메서드의경우 해당 자원이 애플리케이션에 존재한다면 자원의 데이터와 함께 200 OK 상태코드를 포함하여야 하고, 만약 존재하지 않는다면 해당 자원을 생성하고 201 CREATED 상태코드를 리턴해야 한다.

마지막으로 deleteCoffee() 메서드는 CrudRepository에 내장된 deleteById() 메서드를 사용해서 간단하게 리팩토링 할 수 있다.

@DeleteMapping("/{id}")
void deleteCoffee(@PathVariable String id) {
    coffeeRepository.deleteById(id);
}

추가

애플리케이션이 시작 될때 자동으로 코드를 실행하는 방법은 여러가지가 있다.

CommandLineRunner 또는 ApplicationRunner 또는 lambda를 지정하여 실행하는 방법이 있지만.

교재에서는 @Component클래스와 @PostConstruct 메서드를 사용하는 것을 소개하고 있다.

아래 작성된 DataLoader클래스는 샘플 데이터를 생성하는 로직을 DataLoader클래스의 loadData()메서드에 추가하고 @PostConstruct 어노테이션을 추가하면 RestApiDemocontroler를 컨트롤러 자체의 목적 만을 수행하도록 변경할 수 있다.

@Component
public class DataLoader {
    private final CoffeeRepository coffeeRepository;

    public DataLoader(CoffeeRepository coffeeRepository) {
        this.coffeeRepository = coffeeRepository;
    }

    @PostConstruct
    private void loadData() {
        coffeeRepository.saveAll(List.of(
                new Coffee("Cafe Cereza"),
                new Coffee("Cafe Ganador"),
                new Coffee("Cafe Lareno"),
                new Coffee("Cafe Tres Pontas")
        ));
    }
}
profile
조금씩,하지만,자주

0개의 댓글

관련 채용 정보