ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot와 AWS로 혼자 구현하는 웹 서비스 - 3장 Spring Boot에서 데이터베이스 다뤄보자
    Spring 2022. 2. 26. 14:59

     

    웹 서비스를 개발하고 운영하다 보면 피할 수 없는 문제가 데이터베이스를 다루는 일

    필자가 스프링을 배울 때는 iBatis와 같은 SQL 매퍼를 이용해서 데이터베이스의 쿼리를 작성했습니다.

    그러다 보니 실제로 개발하는 시간보다 SQL을 다루는 시간이 더 많았습니다.

    이것이 이상하게 느껴졌습니다. 분명 "객체지향 프로그래밍"을 배웠는데 왜 객체지향 프로그래밍을 못 하지?

     

    객체 모델링보다는 테이블 모델링에만 집중하고, 객체를 단순히 테이블에 맞추어 데이터 전달 역할만 하는 개발은 분명 기형적인 형태였습니다.

     

    문제의 해결책으로 JPA라는 자바 표준 ORM 기술을 만나게 됩니다.

     

    이번 시간에는 JPA를 프로젝트에 적용해 보겠습니다.

     

    JPA 소개

    Oracle, MySQL 등을 쓰지 않는 웹 애플리케이션은 거의 없습니다. 

    그러다 보니 객체를 관계형 데이터베이스에서 관리하는 것이 무엇보다 중요합니다.

    관계형 데이터베이스가 계속해서 웹 서비스의 중심이 되면서 모든 코드는 SQL 중심이 되어갑니다.

     

    이는 관계형 데이터베이스가 SQL만 인식할 수 있기 때문인데, SQL로만 가능하니 각 테이블마다 기본적인 CRUD SQL을 매번 생성해야 합니다.

     

    개발자가 아무리 자바 클래스를 아름답게 설계해도, SQL을 통해야만 테이터베이스에 저장하고 조회할 수 있습니다.

     

    실제 현업에서는 수십, 수백 개의 테이블이 있는데, 이 테이블의 몇 배의 SQL을 만들고 유지 보수해야만 합니다. 단순 반복 작업을 수백 번 해야 하는 것만큼 스트레스 쌓이는 일은 없습니다.

     

    이런 단순 반복 작업의 문제 외에도 한 가지 문제가 더 있습니다. 그건 바로 패러다임  불일치 문제입니다. 관계형 데이터베이스는 어떻게 데이터를 저장할지에 초점이 맞춰진 기술입니다.

     

    반대로 객체지향 프로그래밍 언어는 메시지를 기반으로 기능과 속성을 한 곳에서 관리하는 기술입니다.

     

    관계형 데이터베이스와 객체지향 프로그래밍 언어의 패러다임이 서로 다른데, 객체를 데이터베이스에 저장하려고 하니 여러 문제가 발생합니다. 이를 패러다임 불일치라고 합니다.

     

    JPA는 서로 지향하는 바가 다른 2개 영역을 중간에서 패러다임 일치를 시켜주기 위한 기술입니다.

    즉, 개발자는 객체지향적으로 프로그래밍을 하고, JPA가 이를 관계형 데이터베이스에 맞게 SQL을 대신 생성해서 실행합니다.

     

    실무에서 JPA

    실무에서 JPA를 사용하지 못하는 가장 큰 이유로 높은 러닝 커브를 이야기합니다.

    JPA를 잘 쓰려면 객체지향 프로그래밍과 관계형 데이터베이스를 둘 다 이해해야 합니다.

     

    하지만 그만큼 JPA를 사용해서 얻는 보상은 큽니다.

    가장 먼저 CRUD쿼리를 직접 작성할 필요가 없습니다. 또한, 부모-자식 관계 표현, 1:N 관계 표현, 상태와 행위를 한 곳에서 관리하는 등 객체지향 프로그래밍을 쉽게 할 수 있습니다.

     

    속도 이슈는 없을까 하는 걱정이 있을 거라 생각합니다.

    필자는 포털 서비스와 이커머스에서 모두 JPA 기술들을 사용해 보면서 높은 트래픽과 대용량 데이터 처리를 경험해보았습니다.

    JPA에서는 여러 성능 이슈 해결책들을 이미 준비해놓은 상태이기 때문에 이를 잘 활용하면 네이티브 쿼리만큼의 퍼포먼스를 낼 수 있습니다.

     

    요구사항 분석

    게시판을 만들어 보고 AWS에 무중단 배포 하는것까지 진행합니다.

     

    게시판 기능

     - 게시글 조회

     - 게시글 등록

     - 게시글 수정

     - 게시글 삭제

     

    회원 기능

     - 구글/네이버 로그인

     - 로그인한 사용자 글 작성 권한

     - 보인 작성 글에 대한 권한 관리

     

    자바빈 규약을 생각하면서 getter/setter를 무작정 생성하는 경우가 있습니다.

    이렇게 되면 해당 클래스의 인스턴스 값들이 언제 어디서 변해야하는지 코드상으로 명확하게 구분할 수가 없어, 차후 기능 변경 시 정말 복잡해집니다.

    그래서 Entity 클래스에서는 절대 Setter 메소드를 만들지 않습니다.

    대신, 해당 필드의 값 변경이 필요하면 명확히 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야만 합니다.

     

    잘못된 사용 예

    public class Order{
        public void setStatus(boolean status){
        	this.ststus = status
        }
    }
    
    public void 주문서비스의_취소이벤트(){
    	order.setStatus(false);
    }

     

    올바른 사용 예

    public class Order{
        public void cencelOrder(){
        	this.status = false;
        }
    }
    
    public void 주문서비스의_취소이벤트(){
    	order.cancelOrder();
    }

     

    Setter가 없는 이 상황에서 어떻게 값을 채워 DB에 삽입해야 할까요?

    기본적인 구조는 생성자를 통해 최종값을 채운 후 DB에 삽입하는 것이며, 값 변경이 필요한 경우 해당 이벤트에 맞는 public 메소드를 호출하여 변경하는 것으로 전제로 합니다.

     

    트랜잭션 스크립트

    기존에 서비스에서 비지니스 처리를 담당하던 방식을 트랜잭션 스크립트라고 합니다.

    모든 로직이 서비스 클래스 내부에서 처리됩니다.

    그러다 보니 서비스 계층이 무의미하며, 객체란 단순히 데이터 덩어리 역할만 하게 됩니다.

     

    반면 도메인 모델에서 처리할 경우 다음과 같은 코드가 될 수 있습니다.

    @Transactional
    public Order cancelOrder(int orderId){
        //1)
        Orders order = ordersRepository.findById(orderId);
        Delivery delivery = deliveryRepository.findByOrderId(orderId);
        
        //2-3)
        delivery.cancel();
        
        //4)
        order.cancel();
        
        return order;
    }

    order, delivery가 각자 본인 취소 이벤트 처리를 하며, 서비스 메소드는 트랙잭션과 도메인 간의 순서만 보장해 줍니다.

     

    @RequiredArgsConstructor

    스프링에선 Bean을 주입받는 방식 중 가장 권장하는 방식이 생성자로 주입받는 방식입니다.

    @Autowired는 권장하지 않습니다. 즉 생성자로 Bean 객체를 받도록 하면 @Autowired와 동일한 효과를 볼 수 있다는 것입니다.

     

    @RequiredArgsConstructor
    @Service
    public class PostsService {
        
        private final PostsRepository postsRepository;
    
        @Transactional
        public Long save(PostsSaveRequestDto requestDto){
            return postsRepository.save(requestDto.toEntity()).getId();
        }
    }

    @RequiredArgsConstructor는 final이 선언된 모든 필드를 인자값으로 하는 생성자를 롬복의 @RequiredArgsConstructor가 대신 생성해 준 것입니다.

     

    생성자를 직접 안 쓰고 롬복 어노테이션을 사용한 이유는 간단합니다.

    해당 클래스의 의존성 관계가 변경될 때마다 생성자 코드를 계속해서 수정하는 번거로움을 해결하기 위함입니다.

     

    Entity 클래스

    Entity 클래스를 Request/Esponse 클래스로 사용해서는 안 됩니다.

    Entity 클래스는 데이터베이스와 맞닿은 핵심 클래스 입니다.

    Entity 클래스를 기준으로 테이블이 생성되고, 스키마가 변경됩니다.

    화면 변경은 아주 사소한 기능 변경인데, 이를 위해 테이블과 연결된 Entity 클래스를 변경하는 것은 너무 큰 변경입니다.

    꼭 Entity 클래스와 Controller에서 쓸 Dto는 분리해서 사용해야 합니다.

     

    영속성 컨텍스트

    영속성 컨텍스트란 엔티티를 영구 저장하는 환경 입니다.

    일종의 논리적 개념이라고 보시면 되며, JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈립니다.

     

    JPA의 엔티티 매니저가 활성화된 상태로 트랙잭션 안에서 데이터베이스에서 데이터를 가져오면 이 데이터는 영속성 컨텍스트가 유지된 상태입니다.

    이 상태에서 해당 데이터의 값을 변경하면 트랙잭션이 끝나는 시점에 해당 테이블에 변경분을 반영합니다.

    즉, Entity 객체의 값만 변경하면 별도로 Update 쿼리를 날릴 필요가 없다는 것이죠. 이 개념을 더티 체킹이라고 합니다.

Designed by Tistory.