콘텐츠로 이동

2021 07 03

2021-07-03

JPA

  • 객체 지향 패러다임 - 객체: 방향성 O - 파스칼 표기법 Line, Station, LineStation - 테이블: 방향성 X - 스네이크 표기법 line, station, line_station - 객체는 자유롭게 객체 그래프를 탐색할 수 있어야 함
  • SQL 다룰 때의 문제점 - 필드 하나만 추가하면 SQL 관련된 거 다 수정해야함 - SQL로 조회해온 엔티티를 얼마나 신뢰할 수 있을까?
  • ORM - 객체와 관계형 DB를 매핑 - JPA가 자바의 표준 ORM - Hibernate가 대표적인 프레임워크 - 장점 - 패러다임 불일치 문제 해결 - 반복된 SQL문 작성을 안해도 되니 생산성 향상 - 데이터 접근 추상화 - 유지보수 (Entity 수정만 하면 쿼리는 뚝딱) - JPA가 RDB의 문제점을 해결해주는 부분 - 객체의 상속 - 연관 관계 - 객체의 참조로 연관 관계 저장 가능 - 반환한 엔티티를 신뢰하고 사용하게 해줌 - DAO에서 객체 참조 내용들까지 촤르르 다 가져왔을까?
  • DB 스키마 자동 생성 - JPA는 스키마 자동 생성 시켜줌 - 속성을 추가하면 앱 실행시점에 DB 자동으로 생성함 spring.jpa.hibernate.ddl-auto=create
    create: 기존 테이블 삭제, 다시 생성
    create-drop: create와 같으나 종료 시점에 테이블 drop
    update: 변경된 부분만 반영
    validate: entity와 table 정상 매핑 확인
    none: 사용하지 않음
    
  • JPA에 사용되는 엔티티 정의하기
    @Entity //엔티티 클래스임을 정의
    @Table(name = "station") //매핑할 테이블 지정
    public class Station {
        @Id //PK 지정
        @GeneratedValue(strategy = GenerationType.IDENTITY) //PK 생성 규칙 명시
        private Long id;
    
        @Column(name = "name", nullable = false) //칼럼의 이름을 이용해 지정된 필드를 테이블의 칼럼과 매핑
        private String name;
    
        protected Station() {
        }   
    }
    
  • Spring Data JPA - 반복적으로 Dao나 Repository에 주구장창 CRUD 로직 했었음 - JpaRepository을 사용해보자 - PK 기반의 기본적인 CRUD 제공 - 직접 메서드 이름으로 쿼리 생성 가능
    interface StationRepository extends JpaRepository<Station, String> {
        Station findByName(String name);
    }
    
  • 영속성 컨텍스트 - 엔티티 영구 저장 환경 - 엔티티 매니저로 엔티티 저장/조회시 엔티티 매니저는 영속성 컨텍스트에 엔티티 보관/관리
    - 1차 캐시
    - 동일성 보장
    - 트랜잭션 지원 쓰기 지연
    - 변경 감지
    - 지연 로딩
    

    - 엔티티 생명 주기 - 비영속: 영속성 컨텍스트와 전혀 관계 없음 - 영속: 영속성 컨텍스트에 저장된 상태 - 준영속: 영속성 컨텍스트에 저장되었다가 분리된 상태 - 삭제: 삭제된 상태 - 트랜잭션을 커밋하는 순간 영속성 컨텍스트 ---> DB 반영 - JPA에서 엔티티 저장할 때 연관된 엔티티는 모두 "영속" 상태여야함!

  • JPA 꿀팁 - data.sql을 통해 애플리케이션
  • 연관 관계 - 객체: 단방향, 테이블: 양방향 - 객체를 양방향 연관관계로 둔다면, "연관 관계의 주인"을 찾아줘야 함 - 연관 관계는 "다대일", "일대다", "일대일", "다대다"
  • 다대일
    @Entity
    public class Line {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(nullable = false)
        private String name;
    }
    
    @Entity
    public class Station {
        @Id
        @GeneratedValue()
        private Long id;
    
        @Column(nullable = false)
        private String name;
    
        @ManyToOne //다대일 매핑 정보 제공
        @JoinColumn(name = "line_id") //칼럼 이름과 외래 키가 참조할 칼럼
        private Line line;
    }
    
  • 일대다 + 양방향 매핑
    @Entity
    public class Line {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(nullable = false)
        private String name;
    
        @OneToMany(mappedBy = "line") //mappedBy를 통해 Station이 어떤 녀석이 FK를 가지고 있는지 말해줌
        private List<Station> stations = new ArrayList<>();
    }
    
    @Entity
    public class Station {
        @Id
        @GeneratedValue()
        private Long id;
    
        @Column(nullable = false)
        private String name;
    
        @ManyToOne //다대일 매핑 정보 제공
        @JoinColumn(name = "line_id") //칼럼 이름과 외래 키가 참조할 칼럼
        private Line line;
    }
    

    - @OneToMany를 통해 mappedBy,,, 연관관계의 주인의 어떤 필드가 해당 FK를 관리하는지 알려줌 - mappedBy가 없다면 Line-Station 연관관계 테이블 만들어서 관리

  • 연관 관계의 주인? - 객체에서 양방향 연관관계는 없어 - 단방향 2개로 이루어진 것 - 지하철역(다) - 노선(일) - 노선(일) - 지하철역(다) - "외래키" 관리자!! - KEY를 가지고 있는 사람! - 주로 "다" 쪽이 FK를 가지고있어 - "연관 관계의 주인"만이 DB 연관 관계와 매핑되고 외래 키를 등록/수정/삭제 가능 - 주인 아닌 쪽은 읽기만 가능
    @Test
    void fkRuler() {
        final Line line = lineRepository.findByName("3호선");
        final Station 대화역 = stationRepository.save(new Station("대화역"));
        line.getStations().add(대화역);
    }
    
          - 해당 테스트에서 Line 같은 경우 연관관계의 주인이 아니야!!
          - 따라서 getStations().add() 이렇게 해도 Station에 line_id를 부여해줄 능력이 없어!
    

    - 양방향 연관 관계에서는 결국 양쪽 다 신경 써줘야함! java public void addStations(Station station) { station.setLine(this); stations.add(station); } public void setLine(Line line) { if (Objects.nonNull(this.line)) { this.line.getStations().remove(this); } this.line = line; line.getStations().add(this); }

  • FK를 쥐여주는 두 가지 방법 - 다(Station):1(Line) 에서 Station이 JoinColumn으로 가지고 있는 경우
    @Entity
    public class Line {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    }
    
    @Entity
    public class Station {
        @Id
        @GeneratedValue()
        private Long id;
    
        @ManyToOne //다대일 매핑 정보 제공
        @JoinColumn(name = "line_id") //칼럼 이름과 외래 키가 참조할 칼럼
        private Line line;
    }
    

    - 1(Member):다(Favorite) 에서 Member가 JoinColumn으로 가지고 있는 경우 - 이러면 Favorite 저장하고, "update" 쿼리를 통해 Favorite에 member_id 우겨 넣음

    @Entity
    public class Member {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @OneToMany
        @JoinColumn(name = "member_id") //Favorite에 member_id FK 추가
        private List<Favorite> favorites = new ArrayList<>();
    }
    
    @Entity
    public class Favorite {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    }
    

  • 일대일 연관관계 - 소스 엔티티 <-> 타킷 엔티티 - FK를 어느곳에 두어야 할까?
  • CASCADE - 상황에 필요하게 테스트 써가면서 차츰차츰 추가