<JPA 연관관계 설정 >
JPA의 연관 관계 설정이 객체 지향 프로그래밍과 관계형 데이터베이스의 차이를 극복하기 위한 방법입니다.
- @ONE TO ONE
한 엔티티가 다른 엔티티와 1:1로 연결된 경우에 사용됩니다.
- @ONE TO MANY
한 엔티티가 여러 엔티티와 연결된 경우에 사용됩니다.
// 양방향 맵핑에서는 실제 테이블에 List가 세팅되지 않습니다.
// 엔터티 안에서만 사용하는 가상의 컬럼입니다.
// 상대방 엔터티의 갱신에 관여할 수 없기 때문에 단순히 읽기 전용(조회)으로만 사용하는 것을 권장.
@OneToMany(mappedBy = "department") // 연관관계 엔터티의 필드명을 작성.
private List<Employee> employees;
- @MANY TO ONE
여러 엔티티가 한 엔티티와 연결된 경우에 사용됩니다.
- 단 방향 연관 관계
한 쪽 엔티티만 다른 쪽 엔티티를 참조하는 관계입니다. 이를 통해 해당 엔티티는 관련된 엔티티를 조회할 수 있지만, 반대 방향으로는 조회할 수 없습니다.
- 양 방향 연관 관계
양쪽 엔티티 모두 관련된 엔티티를 조회할 수 있습니다.
// 연관관계 편의 메서드 (양방향에서 연관 필드가 수정될 경우
// 실제 테이블과의 데이터를 맞춰주기 위한 메서드 선언)
public void changeDepartment(Department department) {
this.department = department;
department.getEmployees().add(this);
}
- JPA Lazy Loading vs Eager Loading
// EAGER: 해당 필드를 사용 하든 말든 항상 무조건 조인을 수행
// LAZY: 필요한 경우에만 데이터를 가져온다. -> 실무
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_id") // FK 컬럼명 (연관 테이블의 컬럼명과 일치하게)
LAZY가 많이 쓰이닌 확인할필요가 있을듯 하고 FK의 개념을 다시한번 살펴볼 필요가 있다.
- N+1 문제 및 Fetch Join
N+1 문제는 ORM(Object-Relational Mapping)에서 발생하는 성능 문제로, 연관된 엔티티를 처리할 때 발생합니다. 'N+1'이라는 이름은 데이터베이스에 실행되는 쿼리의 수를 나타냅니다.
Fetch Join은 JPQL(JPA Query Language)의 JOIN FETCH 구문을 사용하여 수행됩니다. 다음은 User 엔티티와 연관된 Post 엔티티를 Fetch Join으로 가져오는 예입니다.
큰 데이터 세트의 경우, 한 번에 많은 데이터를 가져오면 메모리를 과도하게 사용하게 되므로, Fetch Join은 적절하게 사용해야 합니다.
- 고아 객체(Orphan Removal)
@Test
@DisplayName("고아 객체 삭제")
void orphanRemovalTest() {
// given
// 2번 부서 조회
Department department
= departmentRepository.findById(2L).orElseThrow();
// 2번 부서 사원 목록 가져오기
List<Employee> employeeList = department.getEmployees();
// when
Employee employee = employeeList.get(1);
employeeList.remove(employee);
// then
}
- CascadeType
// 양방향 맵핑에서는 실제 테이블에 List가 세팅되지 않습니다.
// 엔터티 안에서만 사용하는 가상의 컬럼입니다.
// 상대방 엔터티의 갱신에 관여할 수 없기 때문에 단순히 읽기 전용(조회)으로만 사용하는 것을 권장.
@OneToMany(mappedBy = "department", // 연관관계 엔터티의 필드명을 작성.
cascade = PERSIST, orphanRemoval=true)
private List<Employee> employees;
다대다는 왠만하면 사용하지 않는 것으로 해야합니다. 따라서 데이터베이스 정규화 과정을 통해 다대다 방식을 없애면서 하는 것이 상당히 이롭다. 정신적이나 신체적으로 매우 유리하다고 볼수있다.
다대다가 생기면 모델링과정으로 다시 돌아가는 것이 좋다.
<queryDSL>
- 초기 설정 : build.gradle에 아래와 같이 넣은 후 others 폴더의 compile.java를 실행시키면
generate 파일이 생기는 것을 볼수있다.
// queryDSL
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
tasks.named('test') {
useJUnitPlatform()
}
/**
//querydsl 추가 시작
//queryDsl은 내부적으로 Entity 클래스를 인식해서 그와 비슷한 모양의 QClass를 제작합니다.
//이 QClass를 이용해서 Entity의 타입을 검증할 수 있고, 그에 맞는 쿼리문을 builder 형태로
//제작합니다.
//밑에 작성된 내용은 QClass의 생성 디렉토리 지정 및 프로젝트 build 시 컴파일 명령문 등을
//세팅해 놓은 코드입니다. */
def querydslDir = "src/main/generated"
sourceSets {
main.java.srcDirs += [ querydslDir ]
}
tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(querydslDir))
}
clean.doLast {
file(querydslDir).deleteDir()
}
config 파일을 만들어서 아래와 같이 설정한다.
// QueryDSL 세팅
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
그러면 JPAQueryFactory를 사용할수있게 되었다.
아래와 같이 조회해올수있다.
String jpqlQuery = "SELECT i FROM Idol i WHERE i.(조회할 칼럼) = ?1";
//when
Idol foundIdol = em.createQuery(jpqlQuery, Idol.class)
.setParameter(1, "가을")
.getSingleResult();
그리고
Idol foundIdol = factory
.select(idol)
.from(idol)
.where(
idol.idolName.eq("리즈")
.and(idol.age.eq(20))
)
.fetchOne();
// fetchOne: 단일 건 조회. 여러 건 조회시 예외 발생.
// fetchFirst: 단일 건 조회. 여러 건 조회되어도 첫번째 값만 반환.
// fetch: List 형태로 반환.
와같이 메서드를 사용할 수있다.
- 메서드 커스텀해서 사용하기
@Override
public List<Idol> findAllSortedByName() {
return factory
.select(idol)
.from(idol)
.orderBy(idol.idolName.asc())
.fetch();
}
오버라이드 쓰면 된다고 합니다 ㅎㅎ
'PlayData 백엔드 부트캠프 정리' 카테고리의 다른 글
다시 시작하는 부트 캠프 하루 후기 4일차 (1) | 2024.10.18 |
---|---|
다시 시작하는 부트 캠프 하루 후기 3일차 (0) | 2024.10.16 |
다시 시작하는 부트 캠프 하루 후기 2일차 (0) | 2024.10.16 |
다시 시작하는 부트 캠프 하루 후기 1일차 (1) | 2024.10.15 |
PlayData 9월 30일 정리 (0) | 2024.10.02 |