본문 바로가기
기술/Spring-Boot

항해99 11/15(월) 스프링 TIL

by Zabee52 2021. 11. 15.

Spring을 이용한 데이터 통신

스프링(Spring)

스프링은 자바를 이용한 클라이언트와 DB의 연결을 수행할 수 있도록 돕는 프레임워크다. 동적인 웹 개발에 많이 쓰인다.

 

스프링의 요소

엔티티(Entity)

- DB에서의 테이블과 같은 역할. JPA를 통해 실제 DB와의 상호작용을 한다.

// @Entity 선언시 JPA를 사용할 때 RDBMS의 테이블과 같은 역할을 하게 된다.
@Entity
public class Person{
    // @Id : 기본키로 지정
    // @GeneratedValue(stratedgy = GenerationType.AUTO) : Auto_increment 설정
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    // @Column(nullable = false) : 테이블의 필드로 지정. 뒤의 옵션은 not null.
    @Column(nullable = false)
    private String name;
}

JPA(Java Persistence API)?

- 자바 내에서 RDBMS를 사용하는 방식을 정의해놓은 인터페이스이다.

- SQL을 직접 작성해 DB에 접근하던 기존 방식과는 달리, 미리 선언해놓은 Entity와 DB의 테이블을 매핑함으로써 자바의 메소드 형식으로 DB에 접근할 수 있게 해준다.

- 큰 장점 중 하나는, RDBMS마다 존재하는 미세한 문법의 차이를 고려할 필요 없이 명령어를 호출해 사용할 수 있다.

 

 

 

컨트롤러(Controller)

- 클라이언트의 요청(request)에 대한 응답(response)를 수행하는 패키지로, 요청에 따라 어떤 처리를 할지 결정하는 역할을 한다.

// @RestController : response 값을 JSON 형태로 반환해줌. 추가로 Repository와 Service를 이용할 수 있는 상태로 만들어준다.
// @RequiredArgsConstructor : @NonNull, final 필드에 대한 생성자를 자동으로 만들어줌.
//                            repository와 service는 생성자로 만들어놓기만 하면 스프링이 알아서 불러온다.
@RequiredArgsConstructor
@RestController
public class PersonController {
    private final PersonRepository personRepository;
    private final PersonService personService;

    // @GetMapping("/url") : GET 방식의 처리. 조회 기능
    @GetMapping("/api/person")
    public List<Person> getPerson(){
        return personRepository.findAll();
    }

    // @PostMapping("/url") : POST 방식의 처리. 삽입 수행
    // POST 방식으로 받아온 데이터는 @RequestBody로 선언된 매개변수에 매핑된다.
    // @RequestBody로 받아오기 위해서는 request를 할 때 반드시
    // Content-Type : application/json 선언을 해줘야 한다.
    @PostMapping("/api/person")
    public Person postPerson(@RequestBody PersonRequestDto requestDto){
        Person person = new Person(requestDto);
        return personRepository.save(person);
    }

    // @PutMapping("/url/{id}") : PUT 방식의 처리. 수정 수행
    // URL로 받아온 데이터는 @PathVariable로 선언된 매개변수에 매핑된다.
    @PutMapping("/api/person/{id}")
    public Long putPerson(@PathVariable Long id, @RequestBody PersonRequestDto requestDto){
        return personService.update(id, requestDto);
    }

    // @DeleteMapping("/url/{id}") : DELETE 방식의 처리. 삭제 수행
    @DeleteMapping("/api/person/{id}")
    public Long deletePerson(@PathVariable Long id){
        personRepository.deleteById(id);
        return id;
    }
}

 

 

리포지토리(Repository)

- 엔티티를 조작하기 위한 인터페이스로, DB에 CRUD 명령을 실행하게 만드는 인터페이스이다.

// 선언만 해놓고 Controller에서 인스턴스를 생성해 사용한다.
public interface PersonRepository extends JpaRepository<Person, Long> {
}

엔티티를 불러오기 위해서는 JpaRepository<엔티티의 클래스, 기본키의 타입>를 상속받아야 한다.

리포지토리 주요 기능은 컨트롤러 설명에 적혀있다. 메소드명으로 쉽게 때려맞출 수 있다.

 

 

서비스(Service)

- 컨트롤러와 리포지토리 사이를 연결해준다.

- 테이블의 데이터(엔티티)를 직접적으로 선언해 관리해주는 유일한 클래스. 다른 곳은 DTO를 통한 간접적인 통신만 시행한다.

- 대표적으로는 레코드 수정(put, update) 기능을 수행.

- 컨트롤러가 지정하는 처리 중 대부분을 수행하는 패키지로, 요청을 받아 수행하게 될 로직들을 하나의 서비스 단위로 묶은 트랜잭션을 생성하여 처리한다.

@RequiredArgsConstructor
// @Service : 서비스임을 선언해줘야 스프링이 서비스로 취급해준다.
@Service
public class PersonService {
    private final PersonRepository personRepository;

    // @Transactional : 해당 메소드 안에서 작동하는 엔티티의 변화는 실제 DB에 반영될 것임을
    //                  선언하는 구문.
    @Transactional
    public Long update(Long id, PersonRequestDto requestDto){
        Person person = personRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("존재하지 않는 데이터입니다.")
        );

        person.update(requestDto);
        return person.getId();
    }
}

DTO(Data Transfer Object)?

- 직접적인 엔티티 인스턴스의 생성 및 접근을 막기 위해 Service를 제외한 공간에서는 DTO라는 엔티티의 속성값만을 가지는 클래스를 이용해 JPA 작업을 수행한다.

- JPA를 이용할때뿐만 아니라, ORM 운용이나, 외부 솔루션과 연결해 데이터를 통신할때도 DTO를 이용한다.

 

 

롬복(Lombok)

롬복은 어노테이션 기반의 라이브로리로, 빈번하게 사용되는 코드들을 간소화할 수 있도록 돕는 역할을 한다.

사실 롬복이 수행하는 많은 기능들은 기존 IDE에서 자동완성을 제공하는 기능(getter/setter, 생성자 등)들이라 반드시 필요하다고 볼 순 없지만, 사용하는 사람들이 많은 라이브러리이므로 알아두는것이 좋을 것이다.

롬복과 눕물. 엌ㅋㅋㅋㅋㅋ

@Getter
@Setter
@RequiredArgsConstructor
public class PersonRequestDto {
    private final String name;
    private final int age;
}

위에 어노테이션 세 줄 붙인 것으로 getter, setter, final 또는 @NonNull 선언이 되어있는 필드에 대한 생성자 정의가 끝났다. 매우 간편하다. 이 외에도 빈 생성자를 생성해주는 @NoArgsConstructor, 모든 필드가 포함되어있는 생성자를 생성해주는 @AllArgsConstructor 외에도 많은 편의성 기능들을 제공해준다.

필드의 변화가 많을때 별도의 품을 들이지 않고 사용할 수 있다는 점에서 편리하며, 확실히 코드가 깔끔하다. 하지만 기존의 방식에서 큰 불편함을 느끼지 않는 사람이라면 그다지 애용할 것 같은 기능은 아니다.

단점 또한 명확하다.

일부 필드의 Getter/Setter 기능을 제한하려면 해당 필드에 접근해 특별히 @Getter(AccessLevel.NONE) 선언을 해줘야하기 때문에 어떻게 보면 오히려 번거로울수도 있다.

추가로, 일반적인 Setter 방식이 아닌 경우(Setter로 받아와 값을 필터링해서 담아주는 경우)에도 같은 방식으로 선언해준 뒤 작업을 수행해줘야하기 때문에 만약 Setter 메소드의 커스텀을 요구하는 필드가 많을 경우에는 오히려 어노테이션을 통한 선언이 클래스의 직관적이지 못 한 부분을 만들어낼 수 있다. 잘 구분해서 사용하자. 아니면 롬복을 사용하기 좋은 구조로 설계를 하든가.

댓글