Spring 정리

eunsiver·2023년 2월 9일
0

@Id사용을 위해
import org.springframework.data.annotation.Id;
를 했는데 에러가 떴다.

has no identifier (every '@Entity' class must declare or inherit at least one '@Id' or '@EmbeddedId' property)

  • 왜 이런 에러가 뜨는 걸까?
  • import jakarta.persistence.Id;와 어떤 차이일까?
  • import jakarta.persistence.Id;

    관계형 DB에서 사용
    JPA에 의해서 정의된 annotation이고, RDBMS의 기준으로만 적용이 된다.

  • import org.springframework.data.annotation.Id;

    JPA에 의해 지원되지 않는 Nosql이나 프레임워크에서 사용한다.

💥 즉, Spring JPA에서 사용할때는 import jakarta.persistence.Id;를 쓰자!


위 코드에서 order에서 delivery를 cascade하는 이유는?

모든 엔티티는 저장하고 싶으면 각자 persist를 해줘야하는데 order만 persist해도 delivery도 같이 persist 되게 하기 위해 이렇게 설정

값 타입은 변경 불가능하게 설계해야 한다.
@Setter를 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만들자.

JPA 스펙상 엔티티나 임베디드 타입(@Embeddable)은 자바 기본 생성자 public 또는 protected로 설정해야 한다. public으로 두는 것 보다는 protected로 설정하는 것이 그나마 더 안전하다.


모든 연관관계는 지연로딩으로 설정

  • LAZY로 설정
  • EAGER는 예측이 어렵고 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1문제가 자주 발생한다.
  • @XToOne(OneToOne,ManyToOne)관계는 기본이 즉시 로딩이므로 직접 지연로딩으로 설정해야 한다.

  • contoller, web: 웹 계층
  • service: 비즈니스 로직, 트랜잭션 처리
  • repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용
  • domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용
  • @Repository: 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
  • @PersistenceContext: 엔티티 매니저 주입
  • @PersistenceUnit: 엔티티 매니터 팩토리 주입
  • @Transactional: 트랜잭션, 영속성 컨텍스트
    readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 영속성 컨텍스트를 플러시 하지 않으므로, 약간의 성능 향상

스프링 필드 주입 대신 생성자 주입을 사용하자

필드 주입

public class MemberService {
    @Autowired
    MemberRepository memberRepository;
 }

생성자 주입

public class MemberService {
   
    private final MemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
  • 생성자 주입 방식을 권장
  • 변경 불가능한 안전한 객체 생성 가능
  • 생성자가 하나면, @Autwired를 생략할 수 있다.
  • final키워드를 추가하면 컴파일 시점에 memberRepository를 설정하지 않는 오류를 체크할 수 있다.

Lombok

@RequiredArgsConstructor
public class MemberService{
	private final MemberRepository memberRepository
}

스프링 데이터 JPA를 사용하면 EntityManager도 주입 가능

@Repository
@RequiredArgsConstructor
public class MemberRepository{
	private final EntityManager em;
}

@RequiredArgsConstructor를 하면 생성자를 만들지 않아도 됨


테스트 케이스를 위한 설정
테스트용 설정 파일을 추가하자
test/resources/application.yml

  • 테스트에서 스프링을 실행하면 이 위치에 있는 설정 파일을 읽는다.

  • 스프링 부트는 datasource 설정이 없으면, 기본적인 메모리 DB를 사용하고, driver-class도 현재 등록된 라이브러리를 보고 찾아 준다.

  • 추가로 ddl-auto도 create-drop모드로 동작한다.
    따라서 데이터소스나, JPA 관련된 별도의 추가 설정을 하지 않아도 된다.


객체 내부에서 집적 접근, getter를 이용한 접근
https://www.inflearn.com/questions/20180/orderitem-%EA%B4%80%EB%A0%A8

객체 외부에서는 당연히 필드에 직접 접근하면 안되지만, 객체 내부에서 필드에 직접 접근해도 문제가 없다.

다만 조회한 엔티티가 프록시 객체인 경우 필드에 직접 접근하면 원본 객체를 가져오지 못하고 프록시 객체의 필드에 직접 접근하게 된다. 일반적인 상황일때는 문제가 없으나, equals, hashcode를 JPA 프록시 객체로 구현할 때 문제가 될 수 있다.

프록시 객체의 equals를 호출했는데 거기서 필드에 직접 접근하면, 프록시 객체는 필드에 값이 없으므로 항상 null이 반환된다.

그래서 JPA 엔티티에서 equals, hashcode를 구현할 때는 getter를 내부에서 사용해야 한다.




addStock, removeStock 실행 시 stock수가 변경될때, 동시성 문제가 발생하지 않는지?

JPA는 동시성 문제를 해결하기 위해 낙관적 락과 비관적 락 2가지 방식을 제공한다.

동시성 문제란? 낙관적 락과 비관적 락이란?
junit으로 mulit thread요청
mulit thread요청이란?


캡슐화 기능

setter 메소드로 재고 수량을 조절하는게 아니라 addStock 메서드, removeStock 메서드를 이용해 재고 수량을 조절하는 것이 왜 객체지향적인지?

https://velog.io/@kshired/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EC%9E%85%EB%AC%B8-%EC%BA%A1%EC%8A%90%ED%99%94


엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴이라고 한다.


폼 객체 Vs 엔티티 직접 사용

엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다.
화면이나 API에 맞는 폼 객체나 DTO를 사용하자.
그래서 화면이나 API 요구사항을 이것들로 처리하고, 엔티티는 최대한 순수하게 유지하자.


엔티티를 변경할 때는 항상 변경 감지를 사용

  • 컨트롤러에서 어설프게 엔티티를 생성하지 말자

  • 트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달하자(파라미터 or DTO)

  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하자

  • 트랜잭션 커밋 시점에 변경 감지가 실행된다.


    실무에서는 회원 엔티티를 위한 API가 다양하게 만들어지는데, 한 엔티티에 각각의 API를 위한 모든 요청 요구 사항을 담기는 어렵다.

엔티티가 변경되면 API 스펙이 변한다.

API 요청 스펙에 맞추어 별도의 DTO를 파라미터로 받는다.

엔티티를 그대로 외부에 노출하거나 엔티티를 파라미터로 받는 것은 하지 않는 것이 좋다.

profile
Let's study!

0개의 댓글