Spring Boot Memory DataBase CRUD(2)

강서진·2023년 12월 11일
0
post-custom-banner

DB를 만들었으니 컨트롤러, 서비스, 모델 등을 만든다. 각각 패키지를 만들어서 관리한다.


Update 오류 수정

update를 하더라도 기존의 내용이 덧씌워지는 게 아니라 기존의 데이터도 그대로 있고, 수정된 내용이 새로운 데이터로 추가되는 오류가 있었다.
db에 있는 데이터인지 확인하는 부분에서 찾은 prevData는 Optional<UserEntity>이다. 이를 dataList.remove()에 넣으면 해당 리스트는 옵셔널을 담은 리스트가 아니므로 삭제되지 않고 있었다. 이 부분을 Optional에서 꺼내 사용하도록 수정한다.

if (prevData.isPresent()){
            dataList.remove(prevData.get());
            dataList.add(data);

제대로 삭제되고 수정된 내용이 같은 Id 밑으로 저장되는 것을 확인할 수 있다.
delete()도 마찬가지로 수정해준다.

DataBaseConfig

@Configuration: Spring 앱이 실행될 때 참고하는 설정 파일로, @Bean 애너테이션이 붙어 있으면 스프링이 빈으로 관리를 하게 된다. Configurtation은 적힌 대로 Spring Context 영역에 bean을 등록하며 사용하고자 하는 서비스, 컨트롤러 등에 필요한 내용을 주입해주는 역할을 한다. 이 방법은 사용할 bean이 다른 .jar 파일에 포함되어 있거나, 다른 패키지에 있는 경우에 사용한다.

그렇지 않은 경우에는 주입이 필요한 변수에 @Autowired를 붙여 자동으로 의존성을 주입하게 만들 수 있다. 이 경우에는 사용하는 모든 빈을 프로젝트에서 접근이 가능하다.
강의에서는 애너테이션을 사용하는데, 그 중에서도 Lombok 방식을 쓴다. Lombok이 제공해주는 RequeiredArgsConstructor를 사용하면, @Service 등의 컴포넌트 애너테이션을 붙이고, 객체 선언시 final을 붙이면 자동으로 만들어진 생성자 메서드에 알아서 주입을 해준다.

@Configuration
public class DataBaseConfig {
    @Bean
    public UserRepository userRepository(){
        return new UserRepository();
    }
}

UserEntity

Entity 인터페이스를 상속한다.
name과 score를 가진다.

@Data
public abstract class Entity implements PrimaryKey{
    private Long id;
}

UserRepository

SimpleDataRepository를 상속한다.
제네릭으로 UserEntity, Long 자료형을 받는다.

public class UserRepository extends SimpleDataRepository<UserEntity, Long> {
	// 추가: 입력한 점수 이상의 데이터 찾아 반환
    public List<UserEntity>  findAllAbove(int score){
        return this.findAll().stream()
                .filter(it -> { return it.getScore()>=score;})
                .collect(Collectors.toList());
    }
}

Repository 인터페이스에서 정의했던 기능에 추가로, 입력한 점수보다 높은 데이터를 찾아 반환하는 메서드를 만들었다. findAll을 사용하여 전체 결과를 받고, 스트림을 받아 필터로 입력받은 점수보다 높은 데이터만 리스트에 담아 반환한다.

UserService

서비스는 데이터베이스와 연결된 레포지토리를 통해 DB의 데이터를 처리해야 한다. @Service는 서비스 로직이 들어가는 빈의 영역이라고 표시해주는 애너테이션이다.

@Service
@RequiredArgsConstructor
public class UserService {

    @Autowired
    private final UserRepository userRepository;

    public UserEntity save(UserEntity user){
        return userRepository.save(user);
    }

    public List<UserEntity> findAll(){
        return userRepository.findAll();
    }

    public Optional<UserEntity> findById(Long id){
        return userRepository.findById(id);
    }

    public void delete(Long id){
        userRepository.delete(id);
    }

	// 추가: 입력한 점수 이상의 데이터 찾아 반환
    public List<UserEntity> filterScore(int score){
        return userRepository.findAllAbove(score);
    }
}

UserRepository 객체를 만들고, @RequiredArgsConstructor를 붙여 자동으로 Spring에서 생성자를 주입받는다. 이 때 스프링은 UserRepository를 찾는데, bean으로 등록되어있지 않으면 찾지 못해 주입을 하지 못한다. 따라서 따로 config 패키지에 DataBaseConfig를 만들어 bean으로 등록해주거나, 컴포넌트로 등록을 해준다.

UserApiController

컨트롤러에서는 요청과 응답을 전달한다. 컨트롤러는 항상 서비스 객체에 요청을 보내야 하며, 컨트롤러가 직접 db에 붙어서 요청하면 안된다.
UserService 객체를 선언하고, @RequiredArgsConstructor를 달아주면 스프링이 알아서 생성자를 주입해준다.

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserApiController {

    private final UserService userService;

    @PutMapping("")
    public UserEntity create(
            @RequestBody UserEntity userEntity
    ){
        return userService.save(userEntity);

    }

    @GetMapping("/all")
    public List<UserEntity> findAll(){
        return userService.findAll();
    }

    @GetMapping("/{id}")
    public UserEntity findById(@PathVariable Long id){
        System.out.println("findById executed");
        return userService.findById(id).get();
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id){
        userService.delete(id);
        System.out.println("delete executed");
    }

    // 10명 이상의 사용자가 있을 때, 70점 이상의 사용자 정보를 찾아주는 메서드 작성하기
    @GetMapping("/score")
    public List<UserEntity> findAllAbove(@RequestParam int score){
        return userService.filterScore(score);
    }
}

@RestController를 사용해 url과 메서드를 맵핑해준다.

create(): id가 전달되었을 때, 해당하는 id의 데이터가 이미 있으면 새로운 내용으로 수정하고, 받은 id가 없으면 생성하도록 @PutMapping, @RequestBody로 UserEntity를 저장한다.
저장할 때는 Service 객체의 save()를 불러 사용하고 UserEntity를 반환한다.
findAll(): 저장된 모든 데이터를 리스트 형태로 반환한다. Service 객체의 findAll() 메서드를 사용한다.
findById(): 저장된 데이터 중 Path variable로 입력받은 id와 일치하는 id를 가진 데이터를 반환한다. Service 객체의 findById() 메서드를 사용한다.
delete(): 전달받은 id와 일치하는 id를 가진 데이터를 리스트에서 삭제한다. Service 객체의 delete() 메서드를 사용한다.
findAllAbove: findAll의 결과를 스트림으로 바꿔, 쿼리 Request Param으로 전달받은 점수보다 높은 점수를 가진 데이터들만 필터링하여 반환한다. Service 객체의 filterScore()를 사용한다.


재사용

아직은 익숙하지 않은 패턴이지만, 인터페이스와 추상클래스를 사용하면 재사용성이 높아지는 것을 확인할 수 있다. 예를 들어, 책을 저장하고 싶다면 Entity를 상속한 새로운 Book Entity를 선언하고, DataRepository를 상속한 Book Repository를 만들어 얼마든지 재사용할 수 있다.

BookEntity.java
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class BookEntity extends Entity {
    private String name;
    private String category;
    private BigDecimal amount;
}
BookRepository.java
@Service
public class BookRepository extends SimpleDataRepository<BookEntity, Long> {
}
BookService.java
@Service
//@RequiredArgsConstructor
public class BookService {

    private final BookRepository bookRepository;
    public BookService(BookRepository bookRepository){
        this.bookRepository = bookRepository;
    }

//    @Autowired
//    private BookRepository bookRepository;

    // create, update
    public BookEntity save(BookEntity bookEntity){
        return bookRepository.save(bookEntity);
    }

    // findAll
    public List<BookEntity> findAll(){
        return bookRepository.findAll();
    }

//     findById
    public Optional<BookEntity> findById(Long id){
        return bookRepository.findById(id);
    }

    public void delete(Long id){
        bookRepository.delete(id);
    }
}
BookApiController.java
@RestController
@RequestMapping("/api/book")
@RequiredArgsConstructor
public class BookApiController {
    private final BookService bookService;

    @PostMapping("")
    public BookEntity create(@RequestBody BookEntity book){
        return bookService.save(book);
    }

    @GetMapping("/all")
    public List<BookEntity> findAll(){
        return bookService.findAll();
    }
}
post-custom-banner

0개의 댓글