TIL - JPA 데이터베이스 (25.04.30)
2-4 데이터베이스 연결(Driver)
- JDBC Driver Manager의 역할
- Application과 DB 간의 통신을 중개.
- 우체부가 편지를 전달하는 것처럼, Driver는 Application의 요청을 DB가 이해할 수 있는 언어로 변환
- DB Driver의 동작 (개요)
- 항공 교통 관제 시스템과 유사. Application과 DB간의 데이터 교환을 조절하고 관리하는 역할
- Driver 동작 방식
- 연결 초기화 > SQL 전송 및 실행 > 결과 처리 > 연결 종료 순서로 동작
- JDBC Driver Manager는 Application이 실행되고 있는 런타임 시점에
1) Connection(연결)을 생성해 쿼리를 요청할 수 있는 상태를 만들어주고
2) Statement(상태)를 생성해 쿼리를 요청하게 해주고
3) ResultSet(결과셋)을 생성해 쿼리 결과를 받아올 수 있게 해줌
4) 위의 세 가지 상태는 꼭 기억하라고 함!
2-5 데이터베이스 데이터를 외부에서 다루기(JDBC)
- Spring Boot의 JDBC 라이브러리
- Spring Boot는 데이터베이스 연결을 쉽게 구성할 수 있도록 다양한 JDBC 드라이버를 지원 > 복잡한 설정 없이 데이터베이스와의 연결을 쉽게 구성
- JDBC Template (QueryMapper)
- JDBC로 직접 SQL 작성 시 SQL쿼리 요청 시 중복 코드 발생
- DB별 예외에 대한 구분 없이 Checked Exception (SQL Exception) 처리
- Connection, Statement 등 자원 관리를 따로 해줘야 (자원 해제를 안하면 메모리 꽉차서 서버가 죽음)
- 이 문제 해결을 위해 Persistence Framework 2가지가 등장
- 1) SQL Mapper : JDBC Template, MyBatis
- 2) ORM : JPA, Hibernate
- JDBC Template (RowMapper)
- SQL Mapper 첫번째 주자로 JDBCTemplate에 RowMapper 탄생
- 쿼리 수행 결과와 객체 필드 매핑
- RowMapper로 응답필드 매핑코드 재사용
- Connection, Statement, ResultSet 반복적 처리 대신
- But, 결과값을 객체 인스턴스에 매핑하는데 여전히 많은 코드 필요
3-2 쿼리 파일 만들기(QueryMapper)
- QueryMapper의 DB의존성 및 중복 쿼리 문제로 ORM이 탄생
- MyBatis > RowMapper가 갖고 있는 단점인 "반복 코드"를 줄이고 "함께있는 프로그램 코드와 쿼리 코드를 분리하여 관리"하기 위해 탄생
- SQL Mapper의 두 번째 주자 ... SQL 쿼리들을 XML 파일에 작성해 코드와 SQL을 분리하고 싶다...
- 코드의 설정(Connection) 부분을 줄이고 실제 sql문에 연결 > 빠른 개발 가능
- map 인터페이스(또는 클래스)와 SQL 쿼리와 ResultSet 매핑을 위한 xml 및 annotation을 사용.
- 한계: 결국 SQL을 직접 작성하는 건 피곤 (DB기능에 종속적) / 테이블마다 비슷한 CRUD 반복. DB 타입 및 테이블에 종속적
- ORM이 해결해야하는 문제점과 해결책
- 문제점: 상속 / 관계 / 탐색 / 밀도 / 식별성
- 해결책: 영속성 컨텍스트(1차 캐시)를 활용한 쓰기지연
- flush() 전까지 최적화. flush()로 전송된 쿼리는 더 이상 최적화되지 않고 commit()으로 반영만 가능.
- 쓰기 지연 효과: 쿼리를 한 번에 전송 / 트랙잭션 종료 시 쿼리는 1번만 전송 / 영속성 상태에서 객체가 생성되었다 삭제되면 실제 DB에는 아무 동작이 전송되지 않을 수 있음
- 즉, 여러가지 동작이 많이 발생해도 쿼리는 트랜잭션당 최적화 되어 최소쿼리만 날라감
- ORM을 사용하는 가장 쉬운 방법: JpaRepository
- JpaRepository< Entity, ID> 인터페이스를 인터페이스에 extends 붙임 ... @NoRepositoryBean 된 상위 인터페이스들의 기능을 포함한 구현체가 프로그래밍 됨(빈 생성을 막고, 상속받으면 생성돼서 사용 가능한 Annotation)
- JpaRepository (마스터 셰프) : 데이터 엑세스를 위한 핵심 기능의 종합적인 요리책(기능)을 제공
- @NoRepositoryBean 인터페이스 (셰프) : 각 인터페이스는 특정 데이터 액세스 방법을 제공하는 전문적인 기술 또는 레시피를 나타냄
- JpaRepository 상속 : 마스터 셰프의 요리책과 셰프의 전문성을 얻음
- SpringDataJpa에 의해 엔티티의 CRUD, 페이징, 정렬 기능 메소드들을 가진 빈이 등록됨(상위 인터페이스들의 기능)
3-6 테이블 객체끼리 관계 만들기
- OneToOne : 꼭 물리적으로 분리되어야 할까? 생각해볼 관계
- OneToMany : 단방향으로 쓰이면 문제가 발생할 수 있다!
- 속도를 위해 기본적으로 FetchType 설정이 LAZY임
- 네 가지 속성을 가짐
- mappedBy : 연관관계의 주인 필드를 선택
- fetch : 글로벌 패치 전략 설정
- cascade : 영속성 전이 기능 사용
- targetEntity : 연관된 엔티티의 타입 정보를 설정
- ManyToOne
- 네 가지 속성을 가짐
- optional (default true) : false로 설정하면 연관된 엔티티가 반드시 있어야 함
- fetch : 글로벌 패치 전략 설정 (기본이 EAGER이나, 실무에서는 LAZY를 기본으로 설정하는 것을 추천)
- cascade : 영속성 전이 기능 사용
- targetEntity : 연관된 엔티티의 타입 정보 설정 (targetEntity = Member.class 식으로 사용)
- JoinColumn
- 외래 키 매핑 시 사용(Join을 요청하기 위한 매핑정보로 쓰임)
- @ManyToOne 어노테이션과 주로 함께 쓰임(조인대상 컬럼 지정기능을 안 쓸거면 생략해도 됨)
- name 속성은 매핑할 외래키의 이름
- 어노테이션을 생략해도 외래 키가 생성됨
- 네 가지 속성을 가짐
- name : 매핑할 외래 키의 이름
- referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명
- foreignKey : 외래 키 제약조건 지정 (테이블 생성 시에만 적용)
- unique/nullable/insertable/updateable/columnDefinition/table : @Column의 속성과 같음
- ManyToMany
- 다대다 설정을 하게 되면 중간 매핑테이블(JoinTable)이 자동으로 생성 됨
- 중간 매핑 테이블은 JPA상에서 숨겨져서(Entity 정의 없이) 관리됨
- 매핑 테이블 관리가 불가능해 실무에서는 잘 사용하지 않음
- 실무에서는 매핑 테이블을 아래와 같은 형태로 직접 정의함
- TableA(@OneToMany) > MappingTable(@ManyToOne, @ManyToOne) >TableB(@OneToMany)
연관관계 관련 의문
- 배울 때는 @OneToMany Mapping보다 Many쪽에 연관관계의 주인 권한을 줘서 @ManyToOne 형태로 연관관계를 설정하는 경우가 많다고 배운 것 같아. 그리고 양방향 맵핑을 하면 데이터의 변경 시 상대쪽 테이블 중 어디에 변화를 줄 것인지에 대해 모호함과 어려움이 있다고 들은 것 같은데, 내가 어떤 부분을 놓치고 있는지 알려줄래?
- Dr.G의 답변

- 일단, 기존에 알고 있던 정보는 맞다고 함.

- @OneToMany 맵핑은 객체 그래프 탐색과 조회 최적화에 도움을 주고, 양방향 관계는 '읽기 전용' 관계로 활용 가능

- 연관관계의 주인을 Many 쪽에 두고, One 쪽은 read-only로 유지하면 변경 책임이 명확해져 실무에서 혼란이 줄어든다고 함!

- 양방향 매핑이 무조건 나쁜게 아니라, 용도에 맞게 사용하는 것이 중요!

- 추가 질문: @OneToMany에서(mappedBy = Many쪽 테이블 명) 이렇게 설정하는 건가?

- @OneToMany(mappedBy = "...")에서 "..."은 Many쪽 테이블에 매핑되는 필드명을 쓴다고 한다! @OneToMany(mappedBy = "parent")라고 해서 parent 테이블 스스로를 참조하는 게 아니었구나!