작성
·
640
0
[LneQuest]
@Entity(name = "LneQuest")
@Table(name = "lne_quests")
@Cacheable
@JsonInclude(JsonInclude.Include.NON_NULL)
//@NamedEntityGraph(name = "LneQuest.all", attributeNodes = [NamedAttributeNode("quizList")])
class LneQuest (
var type: String,
var title: String,
var description: String?,
@Column(name = "user_constraints")
var userConstraints: String = "{}",
@Column(name = "reward_total")
var rewardTotal: Double,
@Column(name = "reward_remain")
var rewardRemain: Double,
@Column(name = "reward_amount")
var rewardAmount: Double,
@Column(name = "reward_currency")
var rewardCurrency: String,
@Column(name = "thumbnail_url")
var thumbnailUrl: String,
@Column(name = "start_at")
var startAt: LocalDateTime,
@Column(name = "end_at")
var endAt: LocalDateTime,
@Column(name = "deleted_at")
var deletedAt: LocalDateTime? = null,
@OneToMany(fetch = FetchType.LAZY, mappedBy = "quest" /* , cascade = [CascadeType.ALL] */)
var quizList: MutableList<LneQuiz> = mutableListOf()
): AbstractJpaPersistable1() {
// 연관관계 추가 method 는 만들어 놓는게 편리하다.
fun addQuiz(quizz: LneQuiz) {
quizz.quest = this
quizList.add(quizz)
}
}
@Entity(name = "LneQuiz")
@Table(name = "lne_quizzes")
@Cacheable
@JsonInclude(JsonInclude.Include.NON_NULL)
open class LneQuiz(
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "quest_id")
var quest: LneQuest,
var type: String,
var title: String,
var description: String? = null,
var answers: String? = null,
@Column(name = "correct_answer")
var correctAnswer: String,
@Column(name = "deleted_at")
var deletedAt: LocalDateTime? = null
): AbstractJpaPersistable() {
}
@Query("select q " +
" from LneQuest q INNER JOIN FETCH LneQuiz qz where 1=1 " +
" and (:type is null or qz.type = :type) " +
" and (:from is null or :from < q.createdAt) " +
" and (:to is null or q.createdAt < :to)", nativeQuery = false)
fun findAllByJpql(@Param("type")type: String?,
@Param("from")from: LocalDateTime?,
@Param("to")to: LocalDateTime?, pageable: Pageable): Page<LneQuest>
페치 조인하니까 N+1 문제가 없이 쿼리 1번만 가는데요 조인되는 Child 테이블 컬럼 조건이 반영이 안되네요...;;;
qz.type = :type Child 테이블 컬럼 조건을 넣어도
페치 조인 쿼리의 경우 Main 테이블 조건으로 들어가네요...
컬럼명이 같은게 있어서 그런건지요???
SELECT lnequest0_.id AS id1_0_0_,
quizlist1_.id AS id1_1_1_,
lnequest0_.created_at AS created_2_0_0_,
lnequest0_.updated_at AS updated_3_0_0_,
lnequest0_.deleted_at AS deleted_4_0_0_,
lnequest0_.description AS descript5_0_0_,
lnequest0_.end_at AS end_at6_0_0_,
lnequest0_.reward_amount AS reward_a7_0_0_,
lnequest0_.reward_currency AS reward_c8_0_0_,
lnequest0_.reward_remain AS reward_r9_0_0_,
lnequest0_.reward_total AS reward_10_0_0_,
lnequest0_.start_at AS start_a11_0_0_,
lnequest0_.thumbnail_url AS thumbna12_0_0_,
lnequest0_.title AS title13_0_0_,
lnequest0_.type AS type14_0_0_,
lnequest0_.user_constraints AS user_co15_0_0_,
quizlist1_.created_at AS created_2_1_1_,
quizlist1_.updated_at AS updated_3_1_1_,
quizlist1_.answers AS answers4_1_1_,
quizlist1_.correct_answer AS correct_5_1_1_,
quizlist1_.deleted_at AS deleted_6_1_1_,
quizlist1_.description AS descript7_1_1_,
quizlist1_.quest_id AS quest_i10_1_1_,
quizlist1_.title AS title8_1_1_,
quizlist1_.type AS type9_1_1_,
quizlist1_.quest_id AS quest_i10_1_0__,
quizlist1_.id AS id1_1_0__
FROM korbit.lne_quests lnequest0_
LEFT OUTER JOIN korbit.lne_quizzes quizlist1_
ON lnequest0_.id = quizlist1_.quest_id
WHERE lnequest0_.type = 'multiple-choice'
AND ( lnequest0_.created_at BETWEEN '2019-03-10T02:00:00.000+0000' AND
'2020-03-10T02:00:00.000+0000' );
답변 6
2
• 자바진영의ORM기술표준
• JPA를 왜 사용해야 하는가?
• JPA동작-저장, 조회
• JPA와상속-저장,조회
• 성능최적화 - 1치 캐쉬
• 성능최적화 - Lazy 로딩
• OSIV 최적화
• 양방향 연관관계 Owner
• QueryDsl 장점
• QueryDsl 쿼리 사용을 위한 Repository 구조 (Custom Repository, Impl, Querydsl4RepositorySupport)
• N+1 문제 해결
. 페이징 & 소팅
• Projections
JPA 관련 강의 5개를 들으면서 정리해 본 주제 인데요.
실무에서 중요한 팁(@OneToMany 에서 batch_size 설정을 통한 N+1 문제 해결, osiv on/off 이슈등) 또는
실무에서 발생할 수 있는 중요 포인트 중에 놓친게 없는지 한번 확인 부탁드려도 될런지요?
2
나누어서 답을 드릴께요^^
qz.type = :type
-> 저는 잘 되더라구요. 다음 코드를 참고해주세요.
참고로 일대다 fetch join에서 다 쪽을 이렇게 필터링 하는 것은 위험합니다. 다음 링크를 참고해주세요.
https://www.inflearn.com/questions/15876
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
private Long id;
private String teamName;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
private Long id;
private String memberName;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
public Team team;
}
public interface TeamRepository extends JpaRepository<Team, Long> {
@Query("select t from Team t join fetch t.members m where m.memberName = :memberName")
List<Team> findTeams(@Param("memberName") String memberName);
}
더불어 일반적인 @OneToMany 를 QueryDsl FetchJoin 으로 작성해 봤는데요
N+1 문제는 잘 해결되는데 페이징 처리는 안되서 혹시 방법이 있는지요?
-> 이 부분은 앞서 말씀드린 것 처럼 활용2편을 보셔야 합니다^^! 궁금해 하시는 모든 내용이 설명되어 있습니다. 활용2편을 차근차근 따라하시면 일대다 fetch join의 문제점과 해결 방안을 모두 이해하실 수 있습니다.
JPQL @Query 어노테이션도 Runtime 에 Syntax 체크를 해주긴 해주더라구요...
QueryDsl 의 경우에는 Syntax 체크가 언어 레벨에서 되는 점이 더 편할 것 같긴 합니다만,
복잡한 쿼리의 경우 QueryDsl 로 작성하는게 더 검증이나 유지보수가 더 편리할까요? (엔터프라이즈 상황에서요)
-> Runtime도 좋지만 컴파일 시점에 되는 것이 확실히 더 편리합니다. 그런데 Querydsl은 자바 코드여서 리펙토링도 가능하고 재사용 가능하도록 구조화도 가능해서 확실히 실무에서 더 좋습니다. 저도 제 주변 개발자 분들도 엔터프라이즈 상황에서 매우 열심히 사용하고 있습니다.
감사합니다.
0
네 잘 정리해주셨네요^^
우선은 이정도만 알고 들어가도 충분합니다. 나머지는 결국 실무에서 부딪히는 상황마다 조금씩 다른데, 5개 강의를 완강하셨으면 충분히 찾아서 극복하실 수 있을 거에요.
감사합니다.
0
0
@EntityGraph(attributePaths = ["quizList"])
@Query(value = "select q " +
" from LneQuest q INNER JOIN q.quizList qz where 1=1 " +
" and (:type is null or q.type = :type) " +
" and (:from is null or :from < q.createdAt) " +
" and (:to is null or q.createdAt < :to) ",
countQuery = "select count(q)" +
" from LneQuest q where 1=1 " +
" and (:type is null or q.type = :type) " +
" and (:from is null or :from < q.createdAt) " +
" and (:to is null or q.createdAt < :to)", nativeQuery = false)
fun findAllByJpql(@Param("type")type: String?,
@Param("from")from: LocalDateTime?,
@Param("to")to: LocalDateTime?, pageable: Pageable): Page<LneQuest>
val jpql =
selectFrom(lneQuest)
.innerJoin(lneQuest.quizList, lneQuiz)
.fetchJoin()
.where(
WhereBuilder()
.optionalAnd(from, { lneQuest.createdAt.after(from) })
.optionalAnd(to, { lneQuest.createdAt.before(to) })
.optionalAnd(type, { lneQuest.type.eq(type) })
)
.orderBy(*ordable(pageable.sort).toTypedArray())
val countJpql =
selectFrom(lneQuest)
.where(
WhereBuilder()
.optionalAnd(from, { lneQuest.createdAt.after(from) })
.optionalAnd(to, { lneQuest.createdAt.before(to) })
.optionalAnd(type, { lneQuest.type.eq(type) })
)
return applyPagination(pageable, jpql, countJpql)
안녕하세요 답변 주셔서 감사합니다.
문의 드리고 싶은 부분들을 정리해서 github 에 데모로 만들어서
이슈를 확인받고 싶은데, 잘 안되네요.... ;;;
qz.type = :type
으로 조건을 걸 수 있는 방법이 있다면 추가로 알려주시면 감사하겠습니다.
(Querydsl 또는 JPQL 어노테이션 쿼리 모두 상관없습니다)
-> 가능하면 저도 H2 db 로 테스트 코드 한번 만들어서 공유드리도록 하겠습니다.
더불어 일반적인 @OneToMany 를 QueryDsl FetchJoin 으로 작성해 봤는데요
N+1 문제는 잘 해결되는데 페이징 처리는 안되서 혹시 방법이 있는지요?
-> 아니면 FetchJoin 시에는 구조적으로 페이징 처리가 안되는지요?
.fetchJoin() 을 제거하면 N+1 문제가 발생하지만 페이징 처리와 Sort 는 모두 잘 됩니다.
JPQL @Query 어노테이션도 Runtime 에 Syntax 체크를 해주긴 해주더라구요...
QueryDsl 의 경우에는 Syntax 체크가 언어 레벨에서 되는 점이 더 편할 것 같긴 합니다만,
복잡한 쿼리의 경우 QueryDsl 로 작성하는게 더 검증이나 유지보수가 더 편리할까요? (엔터프라이즈 상황에서요)
0
안녕하세요. kepha님^^
질문 여부를 떠나서 사실 이 코드는 정상 동작하지 않습니다.
JPA에서 OneToMany 관계를 fetch join 하면 페이징 처리가 불가능합니다. 관련해서 이런 경우 성능을 최적화 할 수 있는 방법을 활용2편에서 설명드립니다.
그리고 적어주신 문제는 추가로 파악해서 도움을 드리고 싶은데, 이 코드만으로는 정확한 이유를 찾기가 쉽지 않네요.
이 문제를 재현할 수 있는 간단한 자바 코드(코틀린X)와 테스트 케이스를 만들고 전체 프로젝트를 압축해서 올려주세요.
감사합니다.