public List<Item> m33(ItemDto dto) {
return factory
.selectFrom(item)
.where(item.color.eq("white") // 동등
.and(item.price.gt(100000)) // AND + 크다
.and(item.qty.isNotNull())) // AND + null 체크
.fetch();
}
// 동등
.where(item.color.eq("white")) // =
.where(item.color.ne("white")) // !=
// 비교
.where(item.price.gt(100000)) // >
.where(item.price.goe(100000)) // >=
.where(item.price.lt(100000)) // <
.where(item.price.loe(100000)) // <=
.where(item.price.between(50000, 100000)) // between
// NULL
.where(item.description.isNull()) // is null
.where(item.description.isNotNull()) // is not null
// IN
.where(item.color.in("red", "yellow", "blue")) // in
.where(item.color.notIn("red", "yellow", "blue")) // not in
// 문자열
.where(item.description.startsWith("최신")) // like '최신%'
.where(item.description.endsWith("최신")) // like '%최신'
.where(item.description.contains("최신")) // like '%최신%'
.where(item.description.like("%최신%")) // like
// 논리 연산
.where(condition1.and(condition2)) // AND
.where(condition1.or(condition2)) // OR
.where(condition.not()) // NOT
public List<Item> m34() {
return factory
.selectFrom(item)
.orderBy(item.qty.asc().nullsFirst()) // asc + null을 먼저
.fetch();
}
.orderBy(item.color.asc()) // 오름차순
.orderBy(item.color.desc()) // 내림차순
.orderBy(item.color.asc(), item.price.desc()) // 다중 정렬
.orderBy(item.qty.asc().nullsFirst()) // null을 처음에
.orderBy(item.qty.desc().nullsLast()) // null을 마지막에
public List<Item> m35(int offset, int limit) {
return factory
.selectFrom(item)
.offset(offset) // 시작 위치 (0부터)
.limit(limit) // 가져올 개수
.fetch();
}
@GetMapping("/list")
public String list(@RequestParam(defaultValue = "1") Integer page) {
int limit = 10;
int offset = (page - 1) * limit; // 0, 10, 20...
List<Item> list = repository.m35(offset, limit);
}
public Object m36() {
return factory
.select(item.qty.min()) // 최솟값
.from(item)
.fetchOne();
}
.select(item.count()) // COUNT(*)
.select(item.qty.sum()) // SUM(qty)
.select(item.qty.avg()) // AVG(qty)
.select(item.qty.max()) // MAX(qty)
.select(item.qty.min()) // MIN(qty)
public List<Tuple> m37() {
return factory
.select(item.color, item.count(), item.price.avg())
.from(item)
.groupBy(item.color) // 색상별로 그룹핑
.having(item.count().gt(5)) // 개수가 5개 초과인 것만
.fetch();
}
// SQL: select color, count(*), avg(price) from tblItem
// group by color having count(*) > 5
public List<Item> m38() {
QItem item2 = new QItem("item2"); // 별칭 필요
return factory
.selectFrom(item)
.where(item.price.goe(
JPAExpressions.select(item2.price.avg()) // 서브쿼리
.from(item2)
))
.fetch();
}
// SQL: select * from tblItem a
// where price >= (select avg(price) from tblItem b)
public List<Tuple> m39() {
QItem item2 = new QItem("item2");
return factory
.select(
item.name, item.price, item.color,
JPAExpressions
.select(item2.price.avg())
.from(item2)
.where(item2.color.eq(item.color)) // 상관 서브쿼리
)
.from(item)
.fetch();
}
// SQL: select name, price, color,
// (select avg(price) from tblItem b where a.color = b.color)
// from tblItem a
public User m40() {
return factory
.selectFrom(user)
.join(user.userInfo, userInfo) // User와 UserInfo를 조인
.where(user.id.eq("hong"))
.fetchOne();
}
// SQL: select u.* from tblUser u
// inner join tblUserInfo ui on u.id = ui.id
// where u.id = 'hong'
public List<User> m41() {
return factory
.selectFrom(user)
.leftJoin(user.userInfo, userInfo) // left outer join
.fetch();
}
// SQL: select u.* from tblUser u
// left join tblUserInfo ui on u.id = ui.id
.join(user.userInfo, userInfo) // inner join
.innerJoin(user.userInfo, userInfo) // inner join (명시적)
.leftJoin(user.userInfo, userInfo) // left outer join
.rightJoin(user.userInfo, userInfo) // right outer join
1개의 엔티티를 조회할 때, 관련 엔티티를 언제 가져올 것인가?
LAZY (지연 로딩):
- 부모 엔티티만 먼저 로드
- 자식 엔티티는 필요할 때 가져옴
- 쿼리 여러 번 실행
EAGER (즉시 로딩):
- 부모 엔티티와 자식 엔티티를 한 번에 로드
- JOIN으로 실행
- 쿼리 1번 실행
@OneToOne // EAGER (기본값)
@ManyToOne // EAGER (기본값)
@OneToMany // LAZY (기본값)
@ManyToMany // LAZY (기본값)
@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Board> board;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private User user;
// m42: 1:N 관계
User user = userRepository.findById("hong").get();
// 1. 부모 테이블 접근
System.out.println(user.getName());
// 2. 자식 테이블 접근 (LAZY면 여기서 쿼리 실행)
for(Board board : user.getBoard()) {
System.out.println(board.getSubject());
}
// m43, m44: 다른 관계에서도 FetchType 동작
public List<ItemDto> m32() {
// select 결과를 바로 DTO로 매핑
// 전제: ItemDto 생성자에 (name, color, qty) 매개변수 필요
return factory
.select(Projections.constructor(
ItemDto.class,
item.name,
item.color,
item.qty
))
.from(item)
.fetch();
}
// 1. Constructor (생성자)
Projections.constructor(ItemDto.class, item.name, item.color)
// 2. Bean (Setter)
Projections.bean(ItemDto.class, item.name, item.color)
// 3. Fields (리플렉션으로 필드 설정)
Projections.fields(ItemDto.class, item.name, item.color)
| 기능 | 표현 | SQL |
|---|---|---|
| 기본 조회 | selectFrom(item).fetch() | select * |
| 특정 컬럼 | select(item.name).from(item) | select name |
| 조건 | where(item.color.eq("white")) | where color = ? |
| AND | .and(item.price.gt(100000)) | and price > ? |
| OR | .or(item.qty.isNull()) | or qty is null |
| 정렬 | orderBy(item.color.asc()) | order by color asc |
| LIMIT | offset(10).limit(5) | offset 10 limit 5 |
| 집계 | select(item.count()).from(item) | select count(*) |
| 그룹 | groupBy(item.color) | group by color |
| 서브쿼리 | JPAExpressions.select(...) | select (...) |
| JOIN | join(user.userInfo, userInfo) | join ... on |
| LEFT JOIN | leftJoin(user.userInfo, userInfo) | left join |
m33: WHERE절 조건들 (eq, gt, in, contains 등)
m34: 정렬 (orderBy)
m35: 페이징 (offset, limit)
m36: 집계함수 (count, sum, avg, max, min)
m37: GROUP BY + HAVING
m38: 서브쿼리 (WHERE절)
m39: 서브쿼리 (SELECT절) - 상관 서브쿼리
m40: INNER JOIN
m41: LEFT/RIGHT JOIN
m42~m44: FetchType (LAZY, EAGER)