실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 요약본

sarah·2023년 4월 7일
0

엔티티 클래스 개발1,2

@Enumerated의 속성값 default는 EnumType.ORDINAL로 필드의 순서값이다.이는 순서가 변경될 경우, 이전 데이터의 정합성이 떨어지므로 EnumType.STRING으로 지정해서 사용하도록 하자

@OneToOne의 관계는 DB에서 어느쪽에 FK를 잡든 entity에서 동일하게 동작하고, 대신 FK를 entity에서 지정해줄때는 자주 accesss 할 곳에 FK를 적어주는것이 좋다.그래서 현재 케이스에선 order에서 delivery가 같이 조회될 가능성이 높아서 order 테이블에 FK를 잡아준다.

@ManyToMany 관계에서는 db구조상 설계할 수 없다 => 그래서 Join Table을 생성해줘야 한다.@ManyToMany 는 실무에서 사용하지 말자 -> 중간테이블에 컬럼을 추가할 수 없고, 세밀하게 쿼리 실행 어려움그래서 CategoryItem을 만들고 @ManyToOne, @OneToMany 로 매핑해서 사용하자.

@Embeddable, @Embedded 둘 중 하나만 선언해도 됨 => 가시성 있게 두 개 모두 선언해서 사용하는걸로

엔티티 식별자는 id 를 사용하고 PK 컬럼명은 ‘member_id’를 사용했다. 이는 엔티티는 타입(Member)가 있으므로 id 필드만으로 쉽게 구분할 수 있지만, 테이블은 타입이 없으므로 구분이 어렵다.(특히 join 시)그리고 관례상 테이블은 ‘테이블명 + id'를 많이 사용한다.

값 타입은 변경 불가능하게 설계해야 한다.@Setter 를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스로 생성하자.JPA 스펙상 엔티티나 임베디드 타입(@Embeddable)은 자바 기본 생성자를 public 또는 protected로 설정해야 한다. 여기선 public으로 두는 것 보다는 protected 로 설정하는 것이 그나마 안전하다. => JPA 가 이런 제약을 두는 이유는 JPA 구현 라이브러리가 객체를 생성할 때 리플렉션 같은 기술을 사용할 수 있도록 지원해야 하기 때문이다.


엔티티 설계시 주의점

  1. 엔티티에는 가급적 Setter를 사용하지 말자
    1. Setter가 모두 열려있으면, 변경 포인트가 너무 많아서 유지보수가 어렵다.
  2. 모든 연관관계는 지연로딩으로 설정!
    1. 즉시로딩(EAGER)은 예측이 어렵고, 어떤 SQL 이 실행될지 추적하기 어렵다 => 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
    2. 실무에서 모든 연관관계는 지연로딩(LAZY)로 설정해야 한다.
    3. 연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.
    4. @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다.
  3. 컬렉션은 필드에서 초기화 하자.
    1. 컬렉션은 필드에서 바로 초기화하는 것이 안전하다 => null 문제에서 안전하다.
    2. 하이버네이트는 엔티티를 영속화 할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다. => 컬렉션을 잘못 생성할 경우 하이버에니트 내부 메커니즘에 문제가 발생할 수 있다.
    3. 따라서 필드레벨에서 생성하는 것이 가장 안전하고 코드가 간결하다.

  • 테이블, 컬럼명 생성 전략

스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다르다.

하이버네이트 기존 구현 : 엔티티의 필드명을 그대로 테이블 명으로 사용 (SpringPhysicalNamingStrategy)

스프링 부트 신규 설정(엔티티(필드) → 테이블(컬럼))
1. 카멜 케이스 → 언더스코어(memberPoing → memberpoint)
2. .(점) →
(언더스코어)
3. 대문자 → 소문자

  • 연관관계 메서드 => 양방향 관계일경우 핵심적으로 컨트롤해주는 곳에 선언하는 것을 선호

도메인 모델 패턴 vs 트랜잭션 스크립트 패턴

비즈니스 로직이 어디서 처리되는가?

두 패턴의 차이점은 서비스에 대한 비즈니스 로직의 위치이다.

Service Layer vs Domain Entity

  • Service Layer
    • Web Layer - Service Layer - Repository Layer 로 나뉘는 웹 계층구조에서 서비스 레이어가 모든 비즈니스 로직을 처리하는 모델 → 트랜잭션 스크립트 패턴
    • 이 때 도메인 엔티티는 아무런 비즈니스 로직이 없는 POJO 클래스로 데이터를 전달하는 역할만 수행한다.
  • Domain Entity
    • 웹 계층구조에서 도메인 엔티티를 좀 더 객체지향적으로 설계하는 패턴이다.
    • 서비스 레이어에 집중된 비즈니스 로직 일부가 도메인으로 이동하여 도메인 엔티티는 도메인 데이터와 함께 비즈니스 로직을 갖는다. → 객체지향적 프로그래밍 방식에 더 가깝다.

그래서 무엇이 정답인가?

[김영한 강의 답변 왈]

도메인이 비즈니스 로직의 주도권을 가지고 개발하는 것을 도메인 주도 설계라 합니다. 이렇게 해두면 서비스의 많은 로직이 엔티티로 이동하고, 서비스는 엔티티를 호출하는 정도의 얇은 비즈니스 로직을 가지게 됩니다.
이렇게 하면 information expert pattern을 지키면서 개발할 수 있습니다.
(information expert pattern는 검색해보시면 도움이 되실거에요^^)
반대로 엔티티는 단순히 getter, setter만 제공하고, 서비스에 비즈니스 로직이 모두 있어도 됩니다.
이렇게 되면 서비스 로직이 커지고, 엔티티는 단순히 데이터를 전달하는 역할만 담당하게 됩니다.
전자는 엔티티를 객체로 사용하는 것이고, 후자는 엔티티를 자료 구조로 사용하는 방식이지요.
그러면 항상 전자가 정답인가? 라고 하면 그렇지는 않습니다. 둘다 장단점이 있기 때문에, 상황에 맞는 적절한 방법을 선택하는 것이 중요합니다.
관련해서 클린코드 6. 객체와 자료구조에 둘의 차이가 자세히 설명되어 있으니 한번 읽어보시길 추천드립니다.

[개인적인 생각]

이 전까지 자연스럽게 서비스 계층에 비즈니스 로직을 작성하였다. 그런데 강의를 들으면서 ‘객체지향적으로 설계하면 도메인에 비즈니스 로직이 있는게 더 응집력 있는 것이 아닌가? 란 의문이 들었고, '왜 도메인 엔티티는 항상 POJO 방식으로 사용하였는가?’ 란 생각이 들었다.
이에 대한 해답을 찾기 위해선 많은 케이스들을 접해보고 상황에 따라 어떤것이 적합한지 체험해보는 수 밖에 없을 것 같다.


setter를 사용하지 않고, entity 수정

이 전 강의에서 부터 계속 @Setter 는 가급적 사용하는 걸 권장하지 않는다고 하였다. 관리 지점이 많아지니깐
이유는 납득이 갔는데, 그러면 setter 없이 어떻게 데이터를 수정하는가?! 의문이 들었고 후반부에 설명해준다고 하셔서 마냥 기다렸는데,,,, 속시원히 해결되지 않았다.
결국 setter 없이 사용하는것도 위와 같이 도메인 모델 패턴방식을 사용해서 도메인의 수정이 있으면 도메인 모델 안에 비즈니스 로직을 작성해서 내부에서만 변경 가능하도록 하는것.
즉, 외부에서 접근 가능한 method를 생성해서 그 안에서 수정한다는 것인데,
현업에서는 수정하는 케이스가 다양할텐데, 이를 모두 method로 각각 풀어내는 것인가….?
이에 대해선 좀 더 서치해보고 공부해봐야겠다.

0개의 댓글