소유 측 (FK를 가진 쪽): UserInfo
@OneToOne
@JoinColumn(name = "id")
private User user;
피소유 측: User
@OneToOne(mappedBy = "user")
private UserInfo userInfo;
조회:
User user = userRepository.findById("hong").get();
user.getUserInfo().getAddress(); // User → UserInfo 접근
1 쪽: User
@OneToMany(mappedBy = "user")
private List<Board> board;
N 쪽: Board
@ManyToOne
@JoinColumn(name = "id")
private User user;
조회:
User user = userRepository.findById("hong").get();
user.getBoard().size(); // 게시판 개수
user.getBoard().forEach(b -> {
System.out.println(b.getSubject());
});
문제: 직접 N:N 표현 불가 → 중간테이블 필수
Board ←→ Tagging ←→ Tag (1:N + N:1로 변환)
Board:
@OneToMany(mappedBy = "board")
private List<Tagging> tagging;
Tagging (중간테이블):
@ManyToOne
@JoinColumn(name = "bseq")
private Board board;
@ManyToOne
@JoinColumn(name = "tseq")
private Tag tag;
Tag:
@OneToMany(mappedBy = "tag")
private List<Tagging> tagging;
조회:
Board board = boardRepository.findById(10L).get();
board.getTagging().get(0).getTag().getTag(); // Board → Tagging → Tag
SQL: 테이블 대상
JPQL: 엔티티 대상 (더 권장)
// JPQL - 엔티티를 대상
@Query(value = "select m from Item m")
List<Item> findAll();
// SQL: select * from tblItem
// Native Query - DB 종속적 (비권장)
@Query(value = "select * from tblItem", nativeQuery = true)
List<Item> findAllNative();
// 1개 파라미터
@Query("select m from Item m where m.color = :color")
List<Item> findByColor(@Param("color") String color);
// 객체 파라미터 (SpEL)
@Query("select m from Item m where m.color = :#{#dto.color} and m.price >= :#{#dto.price}")
List<Item> findByCondition(@Param("dto") ItemDto dto);
JPQL의 문제점 → Query DSL이 해결
@Configuration
@RequiredArgsConstructor
public class QueryDSLConfig {
private final EntityManager em;
@Bean
public JPAQueryFactory jPAQueryFactory() {
return new JPAQueryFactory(em);
}
}
@Repository
@RequiredArgsConstructor
public class ItemQueryDSLRepository {
private final JPAQueryFactory factory;
// import static com.test.java.entity.QItem.item;
// 전체 조회
public List<Item> findAll() {
return factory
.selectFrom(item)
.fetch();
}
// 조건 조회
public Item findByName(String name) {
return factory
.selectFrom(item)
.where(item.name.eq(name))
.fetchOne(); // 단일행
}
// 특정 컬럼만
public List<String> findNames() {
return factory
.select(item.name)
.from(item)
.fetch();
}
// 다중 컬럼 (Tuple)
public List<Tuple> findMultiCol() {
return factory
.select(item.name, item.color, item.qty)
.from(item)
.fetch();
}
}
| 메서드 | 반환 타입 | 설명 |
|---|---|---|
| fetch() | List | 전체 행 (없으면 빈 리스트) |
| fetchOne() | T | 단일행 (2개 이상이면 예외) |
| fetchFirst() | T | 첫 행만 (없으면 null) |
| fetchCount() | long | 행 개수 |
.where(item.name.eq("마우스")) // =
.where(item.price.gt(100000)) // >
.where(item.price.goe(100000)) // >=
.where(item.price.between(50000, 100000)) // between
.where(item.color.in("black", "white")) // in
.where(item.name.contains("마우")) // like '%마우%'
.where(item.name.startsWith("마우")) // like '마우%'
.where(item.qty.isNull()) // is null
.where(item.color.eq("white")
.and(item.price.gte(100000))) // AND
.where(item.color.eq("white")
.or(item.price.gte(100000))) // OR
| 특징 | Query Method | JPQL | Query DSL |
|---|---|---|---|
| 단순성 | 가장 쉬움 | 중간 | 중간 |
| 타입 안전성 | 높음 | 낮음 | 높음 |
| 컴파일 오류 감지 | O | X | O |
| 동적 쿼리 | 불가 | 복잡 | 쉬움 |
| 사용 시기 | 단순 조회 | 복잡한 쿼리 | 동적 조건 |
단순 조회 (예: where name = ?)
→ Query Method 사용
복잡한 조인이나 집계함수 (예: select count(*) from ... where ...)
→ JPQL 사용
동적 조건 (사용자 선택에 따라 AND/OR가 달라짐)
→ Query DSL 사용