연관관계 맵핑
R-DB 에서의 관계란?
DB 정규화 : 중복 데이터로 인해 발생하는 데이터 불일치 현상을 해소
정규화를 통해 각각의 DB 테이블들은 중복되지 않은 데이터를 가지게 된다.
연관 관계(Association)
FK 맵핑 하는 방법 두가지
*1대다 관계도 @ManyToOne을 사용해서 명시할 수 있다
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "team_id")
private type name; // 이거는 반드시 해주고 그 다음에 속성을 column으로 직접 달아줄 수 있다
다중성 : Multiplicity
Fetch 전략
다중성과 기본 Fetch 전략
영속성 전이 (CASCADE)
@OneToOne(cascade = CascadeType.PERSIST)
@OneToMany(cascade = CascadeType.ALL)
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE })
Cascade 종류
public enum CascadeType {
ALL, /* PERSIST, MERGE, REMOVE, REFRESH, DETACH */
PERSIST, // cf.) EntityManager.persist()
MERGE, // cf.) EntityManager.merge()
REMOVE, // cf.) EntityManager.remove()
REFRESH, // cf.) EntityManager.refresh()
DETACH // cf.) EntityManager.detach()
}
연관관계는 - 단방향(Unidirectional), 양방향(Bidirectional)로 구성된다
양방향 연관관계
단방향 VS 양방향
단방향 Mapping 만 써도 연관관계 Mapping은 이미 완료된다.
JPA 연관관계도 내부적으로 FK 참조를 기반하여 구현한다. 따라서 본질적으로 참조는 단방향이다
단방향에 비해 양방향은 복잡하고 양방향 연관관계를 맵핑하려면 객체에서 양쪽 방향을 모두 관리해야 함
물리적으로 존재하지 않는 연관관계를 처리하기 위해 mappedBy 속성을 통해 관계의 주인을 정해야 함
단방향을 양방향으로 만들면 반대 방향으로의 객체 그래프 탐색 가능
우선적으로는 단방향 맵핑을 사용하고 반대 방향으로의 객체 그래프 탐색 기능이 필요할 때 양방향을 사용
*주인 Entity는 CRUD가 가능하지만, 주인이 아닌 Entity는 Select만 가능하다(fk field)
양방향 1대1 관계도 만들 수 있다
일대일 식별관계 (FK를 PK로 가지는 관계)
@MapsId를 써서 PK,FK임을 나타낼 수 있다
@MapsId("연결된 필드 이름")로 명시적으로 사용 가능
하위에서 mappedId로 가져온 객체와 Mapping
단방향 다대일 (N:1) 관계 <-> 일대다 관계(1:N) 관계
@ManyToOne
@OneToMany 달기 나름이다
단방향 일대다 단점 : 다른 테이블에 FK가 있으면 연관관계 처리를 위해 추가적인 UPDATE 쿼리를 실행해야 한다(Persist시 사용)
-> 해결 : 단방향 일대다 관계보다 양방향 Mapping을 사용하자!
Repository : 도메인 객체에 접근하는 컬렉션과 비슷한 인터페이스를 사용해 도메인과 데이터 맵핑 계층 사이를 중재(mediate) - 일반적 정의
*주의사항 : Repository는 JPA의 개념이 아니라 Spring Framework가 제공하는 것임
Spring Data Repository : data access layer 구현을 위해 반복해서 작성했던, 유사한 코드를 줄일 수 있는 추상화 제공
Repository 설정
public interface ItemRepository extends JpaRepository<T, ID> {
}
Repository의 계층구조
JpaRepository가 제공하는 메소드들이 실제 수행하는 쿼리
// insert / update
<S extends T> S save(S entity);
// select * from Items where item_id = {id}
Optional<T> findById(ID id);
// select count(*) from Items;
long count();
// delete from Items where item_id = {id}
void deleteById(ID id);
// ...
@Repository와 @Spring Data Repository의 차이점
메서드 이름으로 쿼리 생성
public interface ItemRepository {
// select * from Items where item_name like '{itemName}'
List<Item> findByItemNameLike(String itemName);
// select item_id from Items
// where item_name = '{itemName}'
// and price = {price} limit 1
boolean existsByItemNameAndPrice(String itemName, Long price);
// select count(*) from Items where item_name like '{itemName}'
int countByItemNameLike(String itemName);
// delete from Items where price between {price1} and {price2}
void deleteByPriceBetween(long price1, long price2);
}
어떤 키워드를 써야 메소드가 생성 될까? 참고)
https://docs.spring.io/spring-data/jpa/reference/repositories/query-keywords-reference.html#appendix.query.method.subject
https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.query-creation
public interface ItemRepository {
// select * from Items where item_name like '{itemName}'
List<Item> findByItemNameLike(String itemName);
// select item_id from Items
// where item_name = '{itemName}'
// and price = {price} limit 1
boolean existsByItemNameAndPrice(String itemName, Long price);
// select count(*) from Items where item_name like '{itemName}'
int countByItemNameLike(String itemName);
// delete from Items where price between {price1} and {price2}
void deleteByPriceBetween(long price1, long price2);
}
SimpleJpaRepository에서 @Repository가 등록되어있어서 우리가 @Repository 없이 써도 Bean으로 등록이 된다.
**Stream API 중단연산자, 종단연산자 공부해보기
복잡한 쿼리 작성
JPA에서 제공하는 객체지향 쿼리
JPQL : 엔티티 객체를 조회하는 객체지향 쿼리(가아아끔 씀)
Criteria API : JPQL을 생성하는 빌더 클래스(거의 안씀)
Third party library를 이용하는 방법
Querydsl(자주 씀)
jOOQ(가끔 씀)
* ...
JPQL VS Criteria API
JPQL
SELECT DISTINCT post
FROM Post post
JOIN post.postUsers postUser
JOIN postUser.projectMember projectMember
JOIN projectMember.member member
WHERE member.name = 'foooooo'
Criteria API
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<PostEntity> cq = cb.createQuery(PostEntity.class);
Root<PostEntity> post = cq.from(Post.class);
cq.select(post);
TypedQuery<PostEntity> q = em.createQuery(cq);
List<PostEntity> posts = q.getResultList();
=SELECT post FROM PostEntity post