IT

스프링부트 4

Dr. yul 2023. 2. 27. 18:09

김영한 님 강의 <자바 ORM 표준 JPA 프로그래밍> 중. (인프런)

  1. 필드와 컬럼 매핑
    1. 칼럼은 기본적으로 @Column인데,
      1. Enum을 넣고 싶으면 @Enumerated
      2. Date, 시간을 넣고 싶으면 @Temporal - type에 Date, Time, DateTime이 있다. 
        1. 지금은 사실 필요 없다. LocalDate, LocalDateTime 사용할 때는 생략 가능. 최신 하이버네이트 쓸 때. 
      3. 큰 데이터를 넣고 싶으면 @Lob - BLOB, CLOB 매핑
        1. 지정할 수 있는 속성이 없다. String이면 CLOB, 나머지는 BLOB으로 매핑된다. 
      4. 매핑 안 하고 싶을 때 @Transient (DB에 가지 말고 memory에서만 계산해줘. 메모리에서만 쓸래. )
    2. 속성
      1. insertable, updatable - 등록, 변경 가능 여부. 기본은 TRUE
      2. nullable 
      3. unique - 잘 안씀. 이름을 안 넣으면 그냥 랜덤으로 넣어줘 버림. 그래서 @Column(unique = true) 이렇게 안하고 엔티티 단위에서 @Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "personNumber", "isActive" }) }) 해서 넣음. 
      4. columnDefinition - 데이터베이스 컬럼 정보를 직접 줌. 
      5. length - 문자 길이 제약조건, String 타입에만 사용한다. 
      6. precision, scale - BicDecimal 타입에서 사용. 아주 큰 숫자나 소숫점 다룰 때 쓴다.
    3. Enum 쓸 때 주의사항: EnumType은 두 가지. ORDINAL과 STRING. default가 ORDINAL임. ORDINAL은 enum 순서를 데이터베이스에 저장, STRING은 이름을 데이터베이스에 저장. 이게 왜 위험하냐 - ENUM에 하나의 타입이 더 추가된다고 해보자. 원래 user, admin밖에 없었는데 앞에 guest를 추가함 - 그럼 원래 user가 0번째였는데 guest가 0번째가 되는 것. DB가 엉망이 되겠쥬...? 무조건 EnumType.STRING으로 해주자. 
  2.  기본 키 매핑
    1. @Id - 직접 할당시
    2. @GeneratedValue - 자동 생성시
      1. IDENTITY: 데이터베이스에 위임, MYSQL, PostgreSQL, SQL Server, DB2에서 사용. 
        1. 난 모르겠고 DB야 너가 해줘. 
        2. Null로 넣는다. id를 임의로 넣으면 안 됨. 그럼 얘를 DB에 insert할 때 PK 값이 생긴다. 즉, 커밋 칠때까지 PK값을 알 수 없다. 근데 영속성 컨텍스트에서 얘를 쓰려면 무조건 PK값이 있어야 함.. 그래서 울며 겨자먹기로 Identity 전략에서만 persist할 때 바로 DB에 insert 쿼리를 날려버린다. 
      2. SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE
        1. @SequenceGenerator 필요 
        2. 시퀀스 오브젝트를 가져와서 값을 셋팅해주는 것. Call next value 해서 가져옴. 
        3. SequenceGenerator 속성: name(MEM_SEQ_GENERATOR), sequenceName(MEM_SEQ), initialValue (DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다. 기본값 1 - 성능최적화를 위함), allocationSize (성능최적화를 위함) 
          1. initialValue와 allocationSize - allocationSize의 디폴트 값은 50이다. persist를 할 때마다 call을 해야 하니까 어쨌뜬 통신을 해야 해서 성능문제 생길 수 있음. 그래서 미리 1~50을 한 번에 메모리에 꺼내놓고 1부터 쌓음. (메모리에서 키밸류를 가져온다.) 50까지 다 쌓으면 그 다음 51~100번 꺼내 놓음. 
      3. TABLE: 키 생성용 테이블 사용, 모든 DB에서 사용
        1. @TableGenerator 필요
        2. 키 생성 전용 테이블을 하나 만들어서 DB 시퀀스를 흉내내는 전략 - 모든 DB에 적용 가능하나 성능이 떨어질 수 있다. 쓰는 건 SequenceGenerator랑 거의 비슷하게 쓴다. 대신 sequenceName대신 table = "" 해서 테이블 이름을 지정하고, pkColumnValue (시퀀스 컬럼명)를 지정해 준다. 중요한 건 initialValue와 allocationSize - 성능 최적화와 관련 있음. 
        3. 실행을 하면 - table 안에 sequence_name, next_val 등의 column이 생성된다. 
        4. 운영에서는 이 전략 쓰기가 부담스럽다. 
      4. AUTO: 기본값은 이거임. 방언에 따라 위 셋 중 자동 지정, 기본값
    3. 권장하는 식별자 전략
      1. 기본 키 제약 조건: null 아님, 유일, 변하면 안된다. - 변하면 안된다는 게 어렵다!!! 
      2. 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. (자연키 = 주민등록번호 등) - 대리키 (대체키)를 사용하자. 비즈니스와 전혀 상관 없는 애를 사용한다. 에를 들어 주민번호를 사용하다가 정부가 갑자기 주민번호 저장해두면 안된다고 하면 ->.... 망.. 
      3. 권장: Long형 + 대체키(시퀀스, uuid 등) + 키 생성전략 사용 - 조합해서.  
  3. 실전예제: 요구사항 분석과 기본 매핑 : 멤버 한 명이 주문을 넣을 수 있다. 주문 - 여러 상품 넣을 수 있다. 상품 - 여러 주문에 들어갈 수 있다. 이 때 매핑을 어떻게 할 것이냐?
    1. 다음과 같은 테이블 설계 가능
    2. 그럼 처음에는 이렇게 설계한다. (다음 사진)

 

  1. 얘의 문제는 뭘까 - 객체지향스럽지 않다. RDB에 맞춘 설계임. 
    1. 들어가기 전에: Setter는 아무데나 남발하지 않는 것이 유지보수에 좋다. 
    2. 문제는, 객체지향스럽지 않다는 것. 이렇게 하면 Order를 찾고 이 order를 넣은 member를 찾고 싶을 때, 이렇게 찾아야 함. 
    3. 즉, 객체지향스럽지 않다. Order.getMember로 멤버를 찾아야 객체지향스럽다.
    4. 객체그래프 탐색 불가능. 참조가 없으므로 UML도 잘못됨. 참조가 다 끊긴다. Id만 가져와서. 

  1. 연관관계 매핑 기초
    1. 목표
      1. 객체와 테이블 연관관계 차이를 이해 - 객체의 참조와 테이블의 외래 키를 매핑!!!
      2. 용어 이해 - 방향: 단방향/양방향, 다중성: 다대일/일대다/일대일/다대다, 연관관계의 주인 (이게 제일 어렵다): 객체 양방향 연관관계는 관리?이 필요함. 
    2. 단방향 연관관계 
      1. 객체지향적 모델링 

- 이렇게 그냥 Team을 박아버림. 그러면 member를 저장할 때 jpa가 알아서 FK를 찾아서 DB에는 테이블 연관관계로 저장해 준다. 

  1. 양방향 연관관계
    1. 머선 뜻: 단방향 연관관계는 member.getTeam()만 할 수 있음. 양방향은 member.getTeam(), team.getMember() 둘다 가능하게 한 것. 
    2. 테이블 연관관계는 단방향이나 양방향이나 똑같다. FK로 member에서 team을, team에서 member를 조회할 수 있다. 
    3. 객체 연관관계만 달라짐. 요러케. 

- 코딩은 이렇게 한다. @OneToMany annotation에 (mappedby = "team") 해줌. 

- new ArrayList<>();로 초기화해주는 것은 관습임. 그래야 add할 때 null point가 안 뜨니까 .. 라는데 머선말이고.. 나중에 궁금하면 찾아보자. 

  1. 양방향 매핑: 반대 방향으로도 객체 그래프 탐색 가능!!!
    1. 하지만 객체는 엥간하면 단방향이 좋다. 양방향으로 하면 신경쓸게 많다.. 
  2. 여기서 의문. mappedBy가 왜 있을까? 
    1. 객체와 테이블간 연관관계 맺는 차이를 이해해야 이해할 수 있다! 
      1. 객체의 양방향 연관관계 - 단방향 연관관계 2개: 회원 -> 팀 / 팀 -> 회원. 사실은 단방향 연관관계가 두 개인 건데 어거지로 양방향 연관관계라고 하는 거임. 참조값이 두 개. 
      2. 테이블의 양방향 연관관계 - 양방향 연관관계 1개임. 회원 <-> 팀. FK 하나로 연관관계가 양쪽으로 다 됨. 양쪽으로 조인할 수 있다. 사실 방향이 없는 것임. 
      3. 객체의 양방향 관계는 사실 양방향 관계가 아니라 서로 다른 단방향 관계 두 개다! 
        1. 둘 중 하나로 외래 키를 관리해야 한다!

- DB 입장에서는 사실 FK 뭘로 쓰든 상관 없다. 하지만 외래 키를 둘 중 어떤걸로 관리할지 정해야 한다. 이게 바로 연관관계의 주인이다. 

  1. 연관관계의 주인
    1. 객체의 두 관계중 하나를 연관관계의 주인으로 지정
    2. 연관관계의 주인만이 외래 키를 관리(등록, 수정)
    3. 주인이 아닌 쪽은 읽기만 가능 - mappedBy = "team"이라는 거는 team이 주인이라는 것. member에서부터 team을 조회는 할 수 있지만 update하거나 값을 더 넣을 수는 없다. 넣어도 아무 일도 일어나지 않는다!
    4. 주인은 mappedBy 속성을 사용하지 않는다. (mappedBy: 내가 뭐에 의해 매핑이 되어버렸어. 이 의미부터가 주인이 아닌 너낌이자나)
    5. 주인이 아니면 mappedBy 속성 사용 - 이걸로 주인을 구분한다. 
    6. 누구를 주인으로?
      1. 외래 키가 있는 곳을 주인으로 정해라. 여기서는 Member.team이 연관관계의 주인. 그럼 N쪽이 무조건 연관관계의 주인이 됨. 그래야 설계도 깔끔하고 성능도 좋음. 주인을 누구로 정하느냐는 비즈니스적인 논리와 전혀 관계가 없다. 
      2. FK가 member에 있다. Member->team이 진짜 매핑. 연관관계의 주인. team->Member는 가짜 매핑. 조회만 가능. 

  1. 양방향 연관관계 매핑 시 많이 하는 실수 (4분 23초까지만 봄 - 언젠가 마저 보자 양방향 연관관계 할 일 있으)
    1. 가장 많이 하는 실수: 연관관계의 주인에 값을 입력하지 않음. - 가짜매핑으로 update나 persist를 하는 경우. 

- 그래서 요렇게 하면 member에 teamID가 제대로 안 들어간다. Null로 보여짐. 이런 실수 하지 말자!

- 연관관계의 주인에만 값을 넣고 주인이 아닌 것에는 값을 안 넣으면 잘 들어감. 반대로 하면 안 들어간다.