30%
61,600원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 해결됨실전! Querydsl
일대다 조인 질문
안녕하세요 강사님!프로젝트 진행중에 이해가 되지 않은 점이 생겨 질문드립니다..!@Test @DisplayName("유저가 반품, 취소한 상품들을 조회할 수 있다. ") public void findDistinctWithDetailsByMemberId() { // given Member member = Member.builder().build(); Order order = Order.builder() .member(member) .orderNo("123") .build(); Product product1 = Product.builder().build(); Product product2 = Product.builder().build(); Product product3 = Product.builder().build(); OrderDetail orderDetail1 = OrderDetail.builder() .order(order) .product(product1) .statusCode(StatusCodeType.RETURN_COMPLETED.getCode()) .build(); OrderDetail orderDetail2 = OrderDetail.builder() .order(order) .product(product2) .statusCode(StatusCodeType.ORDER_CANCEL.getCode()) .build(); OrderDetail orderDetail3 = OrderDetail.builder() .order(order) .product(product3) .statusCode(StatusCodeType.DELIVERY_DELAY.getCode()) .build(); order.addOrderDetail(orderDetail1); order.addOrderDetail(orderDetail2); order.addOrderDetail(orderDetail3); productRepository.saveAll(List.of(product1, product2, product3)); memberRepository.save(member); orderRepository.save(order); // when List<Order> orders = orderRepository.findDistinctWithDetailsByMemberId(member.getId()); // then assertThat(orders).hasSize(1) .extracting("orderNo") .contains("123"); List<OrderDetail> orderDetails = orders.get(0).getOrderDetails(); assertThat(orderDetails).hasSize(2); } @Override public List<Order> findDistinctWithDetailsByMemberId(Long memberId) { BooleanExpression statusCondition = orderDetail.statusCode.eq(ORDER_CANCEL.getCode()); BooleanExpression orCondition = statusCondition.or(orderDetail.statusCode.eq(RETURN_COMPLETED.getCode())); return queryFactory .selectDistinct(order) .from(order) .join(order.orderDetails, orderDetail).fetchJoin() .where( order.member.id.eq(memberId), orCondition ) .fetch(); }다음과 같이 테스트를 작성했습니다.memberId와 ORDER_CANCEL,RETURN_COMPLETED 상태로 걸러서 최종적으로 원하는 orderDetail이 2개 나올 것으로 예상했습니다. 그러나 예상과 달리 3개의 orderDetail이 나왔습니다.em.flush(); em.clear(); // when List<Order> orders = orderRepository.findDistinctWithDetailsByMemberId(member.getId());when절 이전에 강제로 영속성 컨텍스트를 플러시하고 클리어 해주니 예상대로 orderDetail이 2개가 반환되었습니다. 영속성 컨텍스트에서 어떠한 문제가 있는 것 같은데 감도 안잡혀서 이렇게 질문 드립니다...ㅠㅠ 감사합니다. 아래는 JPA가 날린 쿼리입니다..!select distinct o1_0.order_id, o1_0.address_id, o1_0.created_at, o1_0.deleted_at, o1_0.member_id, od1_0.order_id, od1_0.order_detail_id, od1_0.cancelled_at, od1_0.coupon_id, od1_0.created_at, od1_0.deleted_at, od1_0.delivered_date, od1_0.order_no, od1_0.payment_key, od1_0.price, od1_0.product_id, od1_0.quantity, od1_0.reason, od1_0.status_code, od1_0.updated_at, o1_0.order_name, o1_0.order_no, o1_0.real_price, o1_0.total_price, o1_0.total_used_coupon_price, o1_0.updated_at, o1_0.used_point from orders o1_0 join order_detail od1_0 on o1_0.order_id=od1_0.order_id where o1_0.member_id=? and ( od1_0.status_code=? or od1_0.status_code=? )
- 해결됨실전! Querydsl
혹여 fetchOne() 메서드에서 예외가 발생하시는 분들을 위해서 작성합니다.
fetchOne()은 메서드 내부에서 JPQL의 getSingleResult를 수행하기에 현재 강의 환경에서 fetchOne()을 실행하면 NonUniqueResultException 예외가 발생합니다. 그 이유는 member1, 2, 3, 4 가 테스트 수행 전(@BeforeEach)에 persist 되기에 조회되는 member의 갯수가 4개가 됩니다. 그러므로 NonUniqueResultException 예외가 발생합니다. 참고하시면 좋을 것 같아서 남깁니다!
- 미해결실전! Querydsl
resultFetch 테스트 시 em.flush()를 주석했는데도 insert 쿼리가 발생하는 이유
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 안녕하세요.fetch 테스트 관련해서 제가 맞게 이해한 건지 확인차 질문 드립니다.resultFetch() 테스트를 해봤을 때, before()에서 flush를 주석하고 실행했는데도 insert쿼리가 발생했고, 그 insert 쿼리가 select 쿼리 직전에 실행되는 걸 확인했습니다. 이렇게 되는 이유가, em.persist로 인해 쓰기 지연 저장소에 들어있던 insert 쿼리가 JPQL에 의해 flush 된 거라고 생각이 되는데 맞게 이해한 걸까요? @SpringBootTest @Transactional @Commit class MemberTest { @PersistenceContext EntityManager em; JPAQueryFactory query; @BeforeEach void before() { query = new JPAQueryFactory(em); Team teamA = new Team("teamA"); Team teamB = new Team("teamB"); Member member1 = new Member("1", 10, teamA); Member member2 = new Member("2", 20, teamA); Member member3 = new Member("3", 30, teamB); Member member4 = new Member("4", 40, teamB); em.persist(teamA); em.persist(teamB); em.persist(member1); em.persist(member2); em.persist(member3); em.persist(member4); // em.flush(); // em.clear(); } @Test void resultFetch() { List<Member> fetch = query.selectFrom(member).fetch(); Member fetchOne = query.selectFrom(member).where(member.id.eq(1L)).fetchOne(); Member fetchFirst = query.selectFrom(member).fetchFirst(); Long totalCount = query.select(member.count()).from(member).fetchOne(); } }2024-01-17T23:34:46.349+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* insert for com.querydsl.entity.Team */insert into team (name, id) values (?, ?) 2024-01-17T23:34:46.355+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* insert for com.querydsl.entity.Team */insert into team (name, id) values (?, ?) 2024-01-17T23:34:46.356+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* insert for com.querydsl.entity.Member */insert into member (age, team_id, username, id) values (?, ?, ?, ?) 2024-01-17T23:34:46.357+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* insert for com.querydsl.entity.Member */insert into member (age, team_id, username, id) values (?, ?, ?, ?) 2024-01-17T23:34:46.358+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* insert for com.querydsl.entity.Member */insert into member (age, team_id, username, id) values (?, ?, ?, ?) 2024-01-17T23:34:46.360+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* insert for com.querydsl.entity.Member */insert into member (age, team_id, username, id) values (?, ?, ?, ?) 2024-01-17T23:34:46.402+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* select member1 from Member member1 */ select m1_0.id, m1_0.age, m1_0.team_id, m1_0.username from member m1_0 2024-01-17T23:34:46.473+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* select member1 from Member member1 where member1.id = ?1 */ select m1_0.id, m1_0.age, m1_0.team_id, m1_0.username from member m1_0 where m1_0.id=? 2024-01-17T23:34:46.477+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* select member1 from Member member1 */ select m1_0.id, m1_0.age, m1_0.team_id, m1_0.username from member m1_0 fetch first ? rows only 2024-01-17T23:34:46.512+09:00 DEBUG 23204 --- [ main] org.hibernate.SQL : /* select count(member1) from Member member1 */ select count(m1_0.id) from member m1_0
- 미해결실전! Querydsl
querydsl이 지원 중단되었다고 들었는데, 계속 사용할 메리트가 있을까요? 궁금해서 여쭤봅니다!
querydsl이 지원 중단되었다고 들었는데, 계속 사용할 메리트가 있을까요? 궁금해서 여쭤봅니다!
- 미해결실전! Querydsl
프로젝션된 DTO의 필드 값으로 정렬이 가능할까요?
data class ShopSimpleResponse( val id: Long, val image: String, val name: String, val address: String, val introduce: String, val minPrice: Int, val checkCoupon: Boolean, ) { constructor(shop: Shop) : this( id = shop.id, image = shop.images[0].imgUrl, name = shop.name, address = shop.address, introduce = shop.introduce, minPrice = shop.products.minByOrNull { it.price }?.price ?: 0, checkCoupon = true ) }코틀린 코드인 점 죄송합니다!해당 DTO를 프로젝션해서 DB에서 바로 Page<ShopSimpleResponse> 로 반환하고 싶은데 아무리 찾아봐도 방법을 찾기 못했습니다....querydsl내에서나 @Query 를 사용해서 구현하는 방법이 있는지 궁금합니다.해당 부분을 엔티티의 필드 값으로 바꾸는 것도 고려하고 있는데, 우선 이 상태에서 해결 가능한 방법이 있는지 궁금합니다!
- 미해결실전! Querydsl
쿼리메서드에서 And Or 조건 혼용시 괄호를 표현하는 방법이 있나요?
안녕하세요.JPA 쿼리메서드를 사용하면서 궁금한점이 있어 질문 드립니다.SELECT Col_A, Col_B, Col_C, Col_D FROM TBL_AWHERE Col_A = 'aaa' and Col_B = 'bbb' and (Col_C = 'ccc' or Col_D = 'ddd')일반 쿼리로 위의 WHERE 문의 조건을 쿼리메서드로 작성하고자 하는데 내공이 부족하여 잘 안됩니다.findByCol_AAndCol_BAndCol_COrCol_D(String A, String B, String C, String D)이렇게 작성할 경우WHERE Col_A = 'aaa' and Col_B = 'bbb' and Col_C = 'ccc' or Col_D = 'ddd'이 같이 괄호가 없는 쿼리문으로 작성이 되는거 같습니다.쿼리메서드로 and와 or 조건의 괄호를 묶는 방법이 있을까요?
- 해결됨실전! Querydsl
h2 연결 문제
안녕하세요 querydsl 강의 초반에 환경설정 도중 기본 엔티티 생성 후 Test 하는 부분에서 h2 연결시 지정한 url 이 아니라 자꾸 인메모리 방식으로 연결이 되고 있어 글을 남기게 되었습니다. 사용하고 있는 버전은 다음과 같습니다.java : 17 spring boot : 3.2.1Ide : intellij edu 기타 설정 내용application.yml 파일 및 프로젝트 폴더 구성 h2 실행 및 console 접속은 정상적으로 되고 있습니다. h2 서버를 꺼도 연결되지 않는다는 오류가 나지 않고, show_sql 값등을 변경해도 반영되지 않는 걸 보니 application.yml 에 지정한 내용을 못 읽어오는 것 같아 File > invalidate cache test 폴더 하위에 resources 폴더 및 application.yml 추가 와 같이 시도해보았으나 문제 해결에 실패하였습니다. 혹시 이와같은 문제에 대해 해결방안이 있을까요? 검색해봐도 h2 메모리연결 방법 혹은 h2 접속 자체가 안되는 경우에 대해서만 있네요. 답변 부탁드립니다 🙂
- 미해결실전! Querydsl
q파일 오류
q파일은 생성되었는데 사용이 안됩니다...
- 미해결실전! Querydsl
동적 쿼리가 안되는 이유
@Repository @Log4j2 public abstract class Querydsl4RepositorySupport { // 이 클래스가 다루는 도메인(엔터티)의 클래스 private final Class domainClass; // 도메인 엔터티에 대한 Querydsl 쿼리를 생성하고 실행 private Querydsl querydsl; // 데이터베이스와의 상호 작용을 담당하는 JPA의 핵심 객체 private EntityManager entityManager; // queryFactory를 통해 Querydsl 쿼리를 생성하고 실행합니다. private JPAQueryFactory queryFactory; public Querydsl4RepositorySupport(Class<?> domainClass) { Assert.notNull(domainClass, "Domain class must not be null!"); this.domainClass = domainClass; } // Pageable안에 있는 Sort를 사용할 수 있도록 설정한 부분 @Autowired public void setEntityManager(EntityManager entityManager) { Assert.notNull(entityManager, "EntityManager must not be null!"); // JpaEntityInformation을 얻기 위해 JpaEntityInformationSupport를 사용합니다. // 이 정보는 JPA 엔터티에 대한 메타데이터 및 정보를 제공합니다. JpaEntityInformation entityInformation = JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager); // 이는 Querydsl에서 엔터티의 경로를 생성하는 데 사용됩니다. SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE; // entityInformation을 기반으로 엔티티의 경로를 생성합니다. EntityPath path = resolver.createPath(entityInformation.getJavaType()); this.entityManager = entityManager; // querydsl 객체를 생성합니다. // 이 객체는 Querydsl의 핵심 기능을 사용할 수 있도록 도와줍니다. // 엔터티의 메타모델 정보를 이용하여 Querydsl의 PathBuilder를 생성하고, 이를 이용하여 Querydsl 객체를 초기화합니다. this.querydsl = new Querydsl(entityManager, new PathBuilder<>(path.getType(), path.getMetadata())); this.queryFactory = new JPAQueryFactory(entityManager); } // 해당 클래스의 빈(Bean)이 초기화될 때 자동으로 실행되는 메서드 @PostConstruct public void validate() { Assert.notNull(entityManager, "EntityManager must not be null!"); Assert.notNull(querydsl, "Querydsl must not be null!"); Assert.notNull(queryFactory, "QueryFactory must not be null!"); } // 이 팩토리는 JPA 쿼리를 생성하는 데 사용됩니다. protected JPAQueryFactory getQueryFactory() { return queryFactory; } // 이 객체는 Querydsl의 핵심 기능을 사용하는 데 도움이 됩니다. protected Querydsl getQuerydsl() { return querydsl; } // EntityManager는 JPA 엔터티를 관리하고 JPA 쿼리를 실행하는 데 사용됩니다. protected EntityManager getEntityManager() { return entityManager; } // Querydsl을 사용하여 쿼리의 SELECT 절을 생성하는 메서드입니다. // expr은 선택할 엔터티나 엔터티의 속성에 대한 표현식입니다. protected <T> JPAQuery<T> select(Expression<T> expr) { return getQueryFactory().select(expr); } // Querydsl을 사용하여 쿼리의 FROM 절을 생성하는 메서드입니다. // from은 엔터티에 대한 경로 표현식입니다. protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) { return getQueryFactory().selectFrom(from); } // 이 메서드는 주어진 contentQuery를 사용하여 Querydsl을 통해 JPA 쿼리를 생성하고 실행하고, // 그 결과를 Spring Data의 Page 객체로 변환하는 기능을 제공 protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery) { // 1. contentQuery를 사용하여 JPAQuery 객체를 생성 JPAQuery jpaQuery = contentQuery.apply(getQueryFactory()); // 2. Querydsl을 사용하여 페이징 및 정렬된 결과를 가져옴 List<T> content = getQuerydsl().applyPagination(pageable, jpaQuery).fetch(); // 3. contentQuery를 다시 사용하여 countQuery를 생성 JPAQuery<Long> countQuery = contentQuery.apply(getQueryFactory()); // 4. countQuery를 실행하고 총 레코드 수를 얻음 long total = countQuery.fetchOne(); // 5. content와 pageable 정보를 사용하여 Spring Data의 Page 객체를 생성하고 반환 return PageableExecutionUtils.getPage(content, pageable, () -> total); } // 이 메서드는 contentQuery와 함께 countQuery를 인자로 받아서 사용합니다. // contentQuery를 사용하여 페이징된 결과를 가져오고, countQuery를 사용하여 전체 레코드 수를 얻습니다. protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory, JPAQuery> countQuery) { JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory()); List<T> content = getQuerydsl().applyPagination(pageable, jpaContentQuery).fetch(); JPAQuery<Long> countResult = countQuery.apply(getQueryFactory()); log.info("countResult : " + countResult ); Long total = countResult.fetchOne(); return PageableExecutionUtils.getPage(content, pageable, () -> total); } } // count처리 까지 한것 public Page<Member> applyPagination2(MemberSearchCondition condition, Pageable pageable) { return applyPagination(pageable, contentQuery -> contentQuery.selectFrom(member) .join(member.team, team).fetchJoin() .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ), countQuery -> countQuery .select(member.count()) .from(member) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) ); } private BooleanExpression userNameEq(String userName) { return hasText(userName) ? member.userName.eq(userName) : null; } private BooleanExpression teamNameEq(String teamName) { return hasText(teamName) ? team.name.eq(teamName) : null; } private BooleanExpression ageGoe(Integer ageGoe) { return ageGoe != null ? member.age.goe(ageGoe) : null; } private BooleanExpression ageLoe(Integer ageLoe) { return ageLoe != null ? member.age.loe(ageLoe) : null; }서비스에서 public Page<MemberTeamDTO> search(MemberSearchCondition condition, Pageable pageable) { Page<Member> resultPage = memberTestRepository.applyPagination2(condition, pageable); return resultPage.map(member -> MemberTeamDTO.builder() .memberId(member.getId()) .age(member.getAge()) .userName(member.getUserName()) .teamId(member.getTeam().getId()) .teamName(member.getTeam().getName()) .build()); }컨트롤러 @GetMapping("/v3/members") public ResponseEntity<?> searchMemberV3(MemberSearchCondition condition, Pageable pageable) { Page<MemberTeamDTO> search = memberService.search(condition, pageable); return ResponseEntity.ok().body(search); }이렇게 작성했는데 뭐가 문제인지 페이지와 정렬은 잘되는데 http://localhost:9090/v3/members?teamName=teamA&page=1&sort=id,desc teamA와 teamB 모두 나오고 있습니다.질문1 : 조건이 안 먹고 있는데 왜 그럴까요? 어떤 조건으로 해도 안 먹고 있습니다.질문2 : 여기서 동적 쿼리에서 null 처리를 잘 해야 하는 이유가 null로 들어오면 무시되서 전부 조회가 되기 때문에 null 처리를 잘해야하는 거 맞나요?질문 3 : 예를들어, hasText(userName) ? member.userName.eq(userName) : null; 가 있으면 조건이 userName이 존재한다고 하면 true이지만 받아온 userName이 존재하지 않는 userName이면 null이 반환이 되나요? 질문 4 : null 문제 해결 글을 찾던 와중에@DataJpaTest public class DynamicQueryTest { JPAQueryFactory queryFactory; @Autowired EntityManager em; @BeforeEach void init() { queryFactory = new JPAQueryFactory(em); em.persist(new Member("userA", 10, "ROLE_MASTER")); em.persist(new Member("userB", 20, "ROLE_ADMIN")); em.persist(new Member("userC", 30, "ROLE_USER")); } @Test void dynamicQuery() { // Integer age = 10; // String role = "ROLE_MASTER"; Integer age = null; String role = null; List<Member> result = queryFactory .selectFrom(member) .where(ageAndRoleEq(age, role)) .fetch(); System.out.println("result = " + result); } private BooleanBuilder ageAndRoleEq(Integer age, String role) { return ageEq(age).and(roleEq(role)); } private BooleanBuilder ageEq(Integer age) { if (age == null) { return new BooleanBuilder(); } else { return new BooleanBuilder(member.age.eq(age)); } } private BooleanBuilder roleEq(String roleName) { if (roleName == null) { return new BooleanBuilder(); } return new BooleanBuilder(member.roleName.eq(roleName)); } } return nullSafeBuilder(() -> member.age.eq(age)); } private BooleanBuilder roleEq(String roleName) { return nullSafeBuilder(() -> member.roleName.eq(roleName)); } public static BooleanBuilder nullSafeBuilder(Supplier<BooleanExpression> f) { try { return new BooleanBuilder(f.get()); } catch (IllegalArgumentException e) { return new BooleanBuilder(); } }이렇게 줄일 수 있다고 나왔는데 수업에서는 BooleanExpression을 사용했지만 null 방지를 위해서 BooleanBuilder 을 사용하는건가요? 이렇게 하면 null 방지가 되면서 전체 조회가 안되는 형식인가요?
- 미해결실전! Querydsl
최종 질문) 페이징 & 정렬
제가 제대로 이해하고 있는지 제대로 잡고 넘어가려고 합니다. 질문1먼저 단순한 제목 검색과 페이지처리와 정렬을 구현했을 때fetchCount 대신에 fetchOne을 사용하기 위해서 별도로 count 쿼리를 생성해서 페이지 처리를 해줍니다.페이지와 정렬을 처리할 때 동적 정렬을 처리하기 위해서 OrderSpecifier을 사용해서 동적 정렬 쿼리를 하기 위해서 다음과 같이 설정http://localhost:9090/v2/members?page=1&sort=memberId,desc Pageable로 페이지랑 정렬을 받아서 동적으로 뽑아와서 메소드를 만들고 orderBy에 넣었습니다. 이런식으로 처리하는게 맞나요?질문2@Repository @Log4j2 public abstract class Querydsl4RepositorySupport { // 이 클래스가 다루는 도메인(엔터티)의 클래스 private final Class domainClass; // 도메인 엔터티에 대한 Querydsl 쿼리를 생성하고 실행 private Querydsl querydsl; // 데이터베이스와의 상호 작용을 담당하는 JPA의 핵심 객체 private EntityManager entityManager; // queryFactory를 통해 Querydsl 쿼리를 생성하고 실행합니다. private JPAQueryFactory queryFactory; public Querydsl4RepositorySupport(Class<?> domainClass) { Assert.notNull(domainClass, "Domain class must not be null!"); this.domainClass = domainClass; } // Pageable안에 있는 Sort를 사용할 수 있도록 설정한 부분 @Autowired public void setEntityManager(EntityManager entityManager) { Assert.notNull(entityManager, "EntityManager must not be null!"); // JpaEntityInformation을 얻기 위해 JpaEntityInformationSupport를 사용합니다. // 이 정보는 JPA 엔터티에 대한 메타데이터 및 정보를 제공합니다. JpaEntityInformation entityInformation = JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager); // 이는 Querydsl에서 엔터티의 경로를 생성하는 데 사용됩니다. SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE; // entityInformation을 기반으로 엔티티의 경로를 생성합니다. EntityPath path = resolver.createPath(entityInformation.getJavaType()); this.entityManager = entityManager; // querydsl 객체를 생성합니다. // 이 객체는 Querydsl의 핵심 기능을 사용할 수 있도록 도와줍니다. // 엔터티의 메타모델 정보를 이용하여 Querydsl의 PathBuilder를 생성하고, 이를 이용하여 Querydsl 객체를 초기화합니다. this.querydsl = new Querydsl(entityManager, new PathBuilder<>(path.getType(), path.getMetadata())); this.queryFactory = new JPAQueryFactory(entityManager); } // 해당 클래스의 빈(Bean)이 초기화될 때 자동으로 실행되는 메서드 @PostConstruct public void validate() { Assert.notNull(entityManager, "EntityManager must not be null!"); Assert.notNull(querydsl, "Querydsl must not be null!"); Assert.notNull(queryFactory, "QueryFactory must not be null!"); } // 이 팩토리는 JPA 쿼리를 생성하는 데 사용됩니다. protected JPAQueryFactory getQueryFactory() { return queryFactory; } // 이 객체는 Querydsl의 핵심 기능을 사용하는 데 도움이 됩니다. protected Querydsl getQuerydsl() { return querydsl; } // EntityManager는 JPA 엔터티를 관리하고 JPA 쿼리를 실행하는 데 사용됩니다. protected EntityManager getEntityManager() { return entityManager; } // Querydsl을 사용하여 쿼리의 SELECT 절을 생성하는 메서드입니다. // expr은 선택할 엔터티나 엔터티의 속성에 대한 표현식입니다. protected <T> JPAQuery<T> select(Expression<T> expr) { return getQueryFactory().select(expr); } // Querydsl을 사용하여 쿼리의 FROM 절을 생성하는 메서드입니다. // from은 엔터티에 대한 경로 표현식입니다. protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) { return getQueryFactory().selectFrom(from); } // 이 메서드는 주어진 contentQuery를 사용하여 Querydsl을 통해 JPA 쿼리를 생성하고 실행하고, // 그 결과를 Spring Data의 Page 객체로 변환하는 기능을 제공 protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery) { // 1. contentQuery를 사용하여 JPAQuery 객체를 생성 JPAQuery jpaQuery = contentQuery.apply(getQueryFactory()); // 2. Querydsl을 사용하여 페이징 및 정렬된 결과를 가져옴 List<T> content = getQuerydsl().applyPagination(pageable, jpaQuery).fetch(); // 3. contentQuery를 다시 사용하여 countQuery를 생성 JPAQuery<Long> countQuery = contentQuery.apply(getQueryFactory()); // 4. countQuery를 실행하고 총 레코드 수를 얻음 long total = countQuery.fetchOne(); // 5. content와 pageable 정보를 사용하여 Spring Data의 Page 객체를 생성하고 반환 return PageableExecutionUtils.getPage(content, pageable, () -> total); } // 이 메서드는 contentQuery와 함께 countQuery를 인자로 받아서 사용합니다. // contentQuery를 사용하여 페이징된 결과를 가져오고, countQuery를 사용하여 전체 레코드 수를 얻습니다. protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory, JPAQuery> countQuery) { JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory()); List<T> content = getQuerydsl().applyPagination(pageable, jpaContentQuery).fetch(); JPAQuery<Long> countResult = countQuery.apply(getQueryFactory()); log.info("countResult : " + countResult ); Long total = countResult.fetchOne(); return PageableExecutionUtils.getPage(content, pageable, () -> total); } }fetchCount() → fetchOne()으로 변경 // count처리 까지 한것 public Page<Member> applyPagination2(MemberSearchCondition condition, Pageable pageable) { return applyPagination(pageable, contentQuery -> contentQuery.selectFrom(member) .leftJoin(member.team, team) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ), countQuery -> countQuery .select(member.count()) .from(member) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) ); } private BooleanExpression userNameEq(String userName) { return hasText(userName) ? member.userName.eq(userName) : null; } private BooleanExpression teamNameEq(String teamName) { return hasText(teamName) ? team.name.eq(teamName) : null; } private BooleanExpression ageGoe(Integer ageGoe) { return ageGoe != null ? member.age.goe(ageGoe) : null; } private BooleanExpression ageLoe(Integer ageLoe) { return ageLoe != null ? member.age.loe(ageLoe) : null; } @Service @RequiredArgsConstructor public class MemberService { private final MemberTestRepository memberTestRepository; public Page<MemberTeamDTO> search(MemberSearchCondition condition, Pageable pageable) { Sort sort = pageable.getSort(); PageRequest pageRequest = PageRequest.of( (int) pageable.getOffset(), pageable.getPageSize(), sort ); Page<Member> resultPage = memberTestRepository.applyPagination2(condition, pageRequest); return resultPage.map(member -> MemberTeamDTO.builder() .memberId(member.getId()) .age(member.getAge()) .userName(member.getUserName()) .teamId(member.getTeam().getId()) .teamName(member.getTeam().getName()) .build()); } }Sort를 처리할 때 orderBy할 필요 없이 PageRequest.of로 보내주면 된다고 글을 봐서 동적으로 처리할 수 있도록 Sort sort = pageable.getSort(); PageRequest pageRequest = PageRequest.of( (int) pageable.getOffset(), pageable.getPageSize(), sort );이렇게 처리했습니다. Post맨으로 돌려본 결과 제개 원하는 대로 페이징, 정렬이 되었는데 이런식으로 하는게 맞나 확인하고 싶어서 질문드립니다. 질문3 질문1 OrderSpecifier로 동적인 쿼리 처리하고 있는데 Querydsl4RepositorySupport이거를 사용한 이유가 유지보수와 가독성을 더 높여주는 방법이라 이해했는데 맞나요?
- 미해결실전! Querydsl
페이징, 정렬 다시 정리해서 질문드립니다.
1.이렇게 했을 때 페이지 처리와 정렬이 되었습니다. 여기서 정적으로 하려면 지금 작성한 것처럼 지정해주고 orderBy를 동적으로 처리하려면 동적쿼리 where절 처럼 메소드를 만들어서 MemberSearchCondition같은 곳에 정렬을 받아서 메소드로 만들고 orderBy에 넣어주면 되나요?? 2.Querydsl4RepositorySupport@Repository public abstract class Querydsl4RepositorySupport { private final Class domainClass; private Querydsl querydsl; private EntityManager entityManager; private JPAQueryFactory queryFactory; public Querydsl4RepositorySupport(Class<?> domainClass) { Assert.notNull(domainClass, "Domain class must not be null!"); this.domainClass = domainClass; } @Autowired public void setEntityManager(EntityManager entityManager) { Assert.notNull(entityManager, "EntityManager must not be null!"); JpaEntityInformation entityInformation = JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager); SimpleEntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE; EntityPath path = resolver.createPath(entityInformation.getJavaType()); this.entityManager = entityManager; this.querydsl = new Querydsl(entityManager, new PathBuilder<>(path.getType(), path.getMetadata())); this.queryFactory = new JPAQueryFactory(entityManager); } @PostConstruct public void validate() { Assert.notNull(entityManager, "EntityManager must not be null!"); Assert.notNull(querydsl, "Querydsl must not be null!"); Assert.notNull(queryFactory, "QueryFactory must not be null!"); } protected JPAQueryFactory getQueryFactory() { return queryFactory; } protected Querydsl getQuerydsl() { return querydsl; } protected EntityManager getEntityManager() { return entityManager; } protected <T> JPAQuery<T> select(Expression<T> expr) { return getQueryFactory().select(expr); } protected <T> JPAQuery<T> selectFrom(EntityPath<T> from) { return getQueryFactory().selectFrom(from); } protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery) { JPAQuery jpaQuery = contentQuery.apply(getQueryFactory()); List<T> content = getQuerydsl().applyPagination(pageable, jpaQuery).fetch(); return PageableExecutionUtils.getPage(content, pageable, jpaQuery::fetchCount); } protected <T> Page<T> applyPagination(Pageable pageable, Function<JPAQueryFactory, JPAQuery> contentQuery, Function<JPAQueryFactory, JPAQuery> countQuery) { JPAQuery jpaContentQuery = contentQuery.apply(getQueryFactory()); List<T> content = getQuerydsl().applyPagination(pageable, jpaContentQuery).fetch(); JPAQuery countResult = countQuery.apply(getQueryFactory()); return PageableExecutionUtils.getPage(content, pageable, countResult::fetchCount); } } 여기서 보면 fetchCount를 사용하고 있는데 deprecated가 뜹니다. 이거를 어떻게 수정해줘야 할까요? Querydsl4RepositorySupport을 사용해서 public Page<Member> applyPagination(MemberSearchCondition condition, Pageable pageable) { return applyPagination(pageable, query -> query.selectFrom(member) .leftJoin(member.team, team) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ) ); } public Page<Member> applyPagination2(MemberSearchCondition condition, Pageable pageable) { return applyPagination(pageable, contentQuery -> contentQuery.selectFrom(member) .leftJoin(member.team, team) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe()) ), countQuery -> countQuery .select(member.id) .from(member) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) ); }이렇게 구현한 거에도 1번과 같이 메소드로 orderBy에 넣으면 되는건가요? 1번과 3번의 차이는 코드를 더 가독성있게 하려고 하는건가요 아니면 성능상의 차이점이라던지 더 큰 장점이 있나요?
- 미해결실전! Querydsl
Querydsl 페이징, 정렬 질문
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]페이징, 정렬에 대해서 질문이 있습니다. 1.이렇게 했을 때 페이지 처리와 정렬이 되었습니다.2.OrderSpecifier로 변환 3.Querydsl4RepositorySupport 질문 1 : 1~3의 차이점이 궁금합니다.질문 2 : 상황에 따라서 다르겠지만 1~3중 실무에서는 주로 어떤 것을 사용하나요?질문 3 : 1번과 3번의 차이는 1번으로 했을 때 루트 엔티티 범 위를 넘어가는 동적 정렬 기능이 필요할 때 orderBy에는 한계점이 있어서 Querydsl4RepositorySupport을 선택해서 사용하는 것인가요?질문 4 : Querydsl4RepositorySupport에서 fetchCount들이 있는데 곧 지원이 끊기는데 여기서는 어떻게 변화시켜야할지 잘 모르겠습니다...질문 5이렇게 사용했을 때 정렬을 사용하려고 하면 .orderBy를 사용하면 되는건가요? 아니면 다른 방법이 있나요?
- 해결됨실전! Querydsl
페이징 처리 질문
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]지금 질문은 querydsl 뿐만 아니라 spring data jpa도 섞여있습니다. 여태 Spring Data JPA로 페이징을 처리할 때는Page<BoardEntity> findAllByItemItemId(Long itemId, Pageable pageable); @Query(value = "select b from board b " + " join fetch b.member " + " join fetch b.item " + "where b.item.itemId = :itemId" + " order by b.boardId desc ", countQuery = "select count(b) from board b where b.item.itemId = :itemId") Page<BoardEntity> findAllByItemItemId(@Param("itemId") Long itemId, Pageable pageable);이렇게 처리하고서비스에서 // 상품에 대한 문의글 보기 @Transactional(readOnly = true) @Override public Page<BoardDTO> getBoards(Pageable pageable, Long itemId, String email) { // 회원 조회 MemberEntity findUser = memberRepository.findByEmail(email); log.info("유저 : " + findUser); // 상품 조회 ItemEntity findItem = itemRepository.findById(itemId) .orElseThrow(EntityNotFoundException::new); log.info("상품 : " + findItem); // 조회해올 게시글을 넣을 곳 Page<BoardEntity> findAllBoards = boardRepository.findAllByItemItemId(itemId, pageable); // 댓글이 있으면 답변완료, 없으면 미완료 for(BoardEntity boardCheck : findAllBoards) { if(boardCheck.getCommentEntityList().isEmpty()) { boardCheck.changeReply(ReplyStatus.REPLY_X); } else { boardCheck.changeReply(ReplyStatus.REPLY_O); } } for (BoardEntity boardEntity : findAllBoards) { // 파라미터로 받아온 이메일이 있다면 if (email != null) { // 해당 게시글을 만들때 이메일과 조회한 이메일을 체크 // 그리고 맞다면 읽을 권한주고 없으면 잠가주기 if (boardEntity.getMember().getEmail().equals(findUser.getEmail())) { boardEntity.changeSecret(BoardSecret.UN_LOCK); } else { boardEntity.changeSecret(BoardSecret.LOCK); } } else { boardEntity.changeSecret(BoardSecret.LOCK); } } log.info("조회된 게시글 수 : {}", findAllBoards.getTotalElements()); log.info("조회된 게시글 : {}", findAllBoards); return findAllBoards.map(board -> BoardDTO.toBoardDTO( board, board.getMember().getNickName(), board.getItem().getItemId())); } // 상품에 대한 문의글 전체 보기 @GetMapping("") @Tag(name = "board") @Operation(summary = "문의글 전체 보기", description = "모든 상품에 대한 문의글을 봅니다.") public ResponseEntity<?> getBoards( // SecuritConfig에 Page 설정을 한 페이지에 10개 보여주도록 // 설정을 해서 여기서는 할 필요가 없다. @PageableDefault(sort = "boardId", direction = Sort.Direction.DESC) Pageable pageable, @PathVariable(name = "itemId") Long itemId, @RequestParam(value = "email", required = false) String email) { try { log.info("email : " + email); // 검색하지 않을 때는 모든 글을 보여준다. Page<BoardDTO> boards = boardService.getBoards(pageable, itemId, email); Map<String, Object> response = new HashMap<>(); // 현재 페이지의 아이템 목록 response.put("items", boards.getContent()); // 현재 페이지 번호 response.put("nowPageNumber", boards.getNumber()+1); // 전체 페이지 수 response.put("totalPage", boards.getTotalPages()); // 한 페이지에 출력되는 데이터 개수 response.put("pageSize", boards.getSize()); // 다음 페이지 존재 여부 response.put("hasNextPage", boards.hasNext()); // 이전 페이지 존재 여부 response.put("hasPreviousPage", boards.hasPrevious()); // 첫 번째 페이지 여부 response.put("isFirstPage", boards.isFirst()); // 마지막 페이지 여부 response.put("isLastPage", boards.isLast()); return ResponseEntity.ok().body(response); } catch (Exception e) { return ResponseEntity.badRequest().build(); } }이렇게 처리를 했습니다. Page 기능을 사용해서 구현했고 querydsl에서 페이징처리는 다음과 같이 했습니다. @Override public Page<MemberTeamDTO> searchPageComplex(MemberSearchCondition condition, Pageable pageable) { List<MemberTeamDTO> content = queryFactory .select(new QMemberTeamDTO( member.id.as("memberId"), member.userName, member.age, team.id.as("teamId"), team.name.as("teamName"))) .from(member) .leftJoin(member.team, team) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); // count 쿼리 (조건에 부합하는 로우의 총 개수를 얻는 것이기 때문에 페이징 미적용) long total = queryFactory // SQL 상으로는 count(member.id)와 동일 .select(member.count()) .from(member) // .leftJoin(member.team, team) .where(userNameEq(condition.getUserName()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) .fetchOne(); return new PageImpl<>(content, pageable, total); }fetchResult와 fetchCount가 지원을 안한다고 해서 조건에 따라 카운트를 구해서 총 개수를 구해서 하는 방식으로 했는데 이렇게 했을 때도 Map<String, Object> response = new HashMap<>(); // 현재 페이지의 아이템 목록 response.put("items", boards.getContent()); // 현재 페이지 번호 response.put("nowPageNumber", boards.getNumber()+1); // 전체 페이지 수 response.put("totalPage", boards.getTotalPages()); // 한 페이지에 출력되는 데이터 개수 response.put("pageSize", boards.getSize()); // 다음 페이지 존재 여부 response.put("hasNextPage", boards.hasNext()); // 이전 페이지 존재 여부 response.put("hasPreviousPage", boards.hasPrevious()); // 첫 번째 페이지 여부 response.put("isFirstPage", boards.isFirst()); // 마지막 페이지 여부 response.put("isLastPage", boards.isLast());이런식으로 뽑아서 프론트에 보낼 수 있는지와실무에서도 spring data jpa와 querydsl 방식으로 페이징 처리를 할 때 이렇게 하는지 아니면 다른 방식이 더있는지 궁금해서 질문드립니다.
- 해결됨실전! Querydsl
Hello 테이블 생성 안됨..
안녕하세요.. Hello Table이 생성이 안돼서 질문 드립니다. 모든 코드는 강의자료에 있는 코드를 복붙했습니다. (에러가 나서 ..) test코드를 돌려보면 에러는 안나는데 h2db테이블에서 생성이 안됩니다.에러 코드 > Task :compileJava UP-TO-DATE > Task :processResources > Task :classes > Task :compileTestJava > Task :processTestResources NO-SOURCE > Task :testClasses > Task :test 15:47:24.714 [Test worker] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [study.querydsl.QuerydslApplicationTests]: QuerydslApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration. 15:47:24.762 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration study.querydsl.QuerydslApplication for test class study.querydsl.QuerydslApplicationTests . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.2.0) 2024-01-02T15:47:24.972+09:00 INFO 14318 --- [ Test worker] s.querydsl.QuerydslApplicationTests : Starting QuerydslApplicationTests using Java 17.0.6 with PID 14318 (started by gaheechoi in /Users/gaheechoi/Desktop/휴학/백앤드/querydsl) 2024-01-02T15:47:24.973+09:00 INFO 14318 --- [ Test worker] s.querydsl.QuerydslApplicationTests : No active profile set, falling back to 1 default profile: "default" 2024-01-02T15:47:25.274+09:00 INFO 14318 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode. 2024-01-02T15:47:25.285+09:00 INFO 14318 --- [ Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 7 ms. Found 0 JPA repository interfaces. 2024-01-02T15:47:25.532+09:00 INFO 14318 --- [ Test worker] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default] 2024-01-02T15:47:25.557+09:00 INFO 14318 --- [ Test worker] org.hibernate.Version : HHH000412: Hibernate ORM core version 6.3.1.Final 2024-01-02T15:47:25.573+09:00 INFO 14318 --- [ Test worker] o.h.c.internal.RegionFactoryInitiator : HHH000026: Second-level cache disabled 2024-01-02T15:47:25.685+09:00 INFO 14318 --- [ Test worker] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer 2024-01-02T15:47:25.700+09:00 INFO 14318 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2024-01-02T15:47:25.796+09:00 INFO 14318 --- [ Test worker] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced user=SA 2024-01-02T15:47:25.797+09:00 INFO 14318 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2024-01-02T15:47:25.908+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178045908 | took 3ms | statement | connection 1| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced select * from INFORMATION_SCHEMA.SEQUENCES select * from INFORMATION_SCHEMA.SEQUENCES; 2024-01-02T15:47:26.178+09:00 INFO 14318 --- [ Test worker] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration) 2024-01-02T15:47:26.186+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : drop table if exists hello cascade 2024-01-02T15:47:26.187+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046187 | took 0ms | statement | connection 2| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced drop table if exists hello cascade drop table if exists hello cascade ; 2024-01-02T15:47:26.187+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : drop sequence if exists hello_seq 2024-01-02T15:47:26.187+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046187 | took 0ms | statement | connection 2| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced drop sequence if exists hello_seq drop sequence if exists hello_seq; 2024-01-02T15:47:26.189+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : create sequence hello_seq start with 1 increment by 50 2024-01-02T15:47:26.191+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046191 | took 1ms | statement | connection 3| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced create sequence hello_seq start with 1 increment by 50 create sequence hello_seq start with 1 increment by 50; 2024-01-02T15:47:26.192+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : create table hello (id bigint not null, primary key (id)) 2024-01-02T15:47:26.194+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046194 | took 1ms | statement | connection 3| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced create table hello (id bigint not null, primary key (id)) create table hello (id bigint not null, primary key (id)); 2024-01-02T15:47:26.195+09:00 INFO 14318 --- [ Test worker] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default' 2024-01-02T15:47:26.268+09:00 WARN 14318 --- [ Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2024-01-02T15:47:26.478+09:00 INFO 14318 --- [ Test worker] s.querydsl.QuerydslApplicationTests : Started QuerydslApplicationTests in 1.645 seconds (process running for 2.13) 2024-01-02T15:47:26.801+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : select next value for hello_seq 2024-01-02T15:47:26.803+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046803 | took 0ms | statement | connection 4| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced select next value for hello_seq select next value for hello_seq; 2024-01-02T15:47:26.974+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : insert into hello (id) values (?) 2024-01-02T15:47:26.976+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046976 | took 0ms | statement | connection 4| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced insert into hello (id) values (?) insert into hello (id) values (1); 2024-01-02T15:47:26.986+09:00 DEBUG 14318 --- [ Test worker] org.hibernate.SQL : select h1_0.id from hello h1_0 2024-01-02T15:47:26.987+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178046987 | took 0ms | statement | connection 4| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced select h1_0.id from hello h1_0 select h1_0.id from hello h1_0; 2024-01-02T15:47:27.014+09:00 INFO 14318 --- [ Test worker] p6spy : #1704178047014 | took 0ms | rollback | connection 4| url jdbc:h2:mem:a21f98cc-44fd-4380-bad2-49c71d2c5ced ; OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended 2024-01-02T15:47:27.025+09:00 INFO 14318 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2024-01-02T15:47:27.026+09:00 INFO 14318 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated... 2024-01-02T15:47:27.027+09:00 INFO 14318 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed. > Task :test Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0. You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins. For more on this, please refer to https://docs.gradle.org/8.5/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation. BUILD SUCCESSFUL in 3s 4 actionable tasks: 3 executed, 1 up-to-date 3:47:27 PM: Execution finished ':test --tests "study.querydsl.QuerydslApplicationTests"'. h2 db는 기존의 1.4 xx 대를 사용하다, spring boot3로 올리면서 강의자료 대로 2.2.224로 바꾸었습니다.db 생성은 잘 되었고, tcp로 db접근도 완료 했습니다.생성이 안되는 이유를 모르겠습니다 ㅠㅠ... tcp 접근 QHello 위치 application.yml
- 해결됨실전! Querydsl
Q파일 생성 위치 질문
안녕하세요현재 프로젝트 세팅 및 테스트를 진행 중입니다. spring boot가 3이상으로 제한된 상황속에서 진행중이며, 강의 내용을 따라하고 있습니다! build.gradle은 https://docs.google.com/document/d/1j0jcJ9EoXMGzwAA2H0b9TOvRtpwlxI5Dtn3sRtuXQas/edit#heading=h.vfy9wirpglmx를 복/붙 하였습니다.plugins { id 'java' id 'org.springframework.boot' version '3.2.0' id 'io.spring.dependency-management' version '1.1.4' } group = 'study' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' //test 롬복 사용 testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' //Querydsl 추가 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { useJUnitPlatform() } clean { delete file('src/main/generated') } 질문은 크게 3가지입니다. build/other/compileQuerydsl은 표기가 안되는데 다른 답변을 보니 springboot3으로 올라가며 compileQuerydsl은 표기가 안된다고 하신 것 같은데 맞을까요?정확한 Q파일의 생성 위치가 궁굼합니다.!강의상에서는 src/main에 둬도 괜찮지만, 버전관리 시스템(git)에는 추가 되지 않는게 좋다고 하셨던 것 같은데 맞나요?build에만 Q파일이 생성되는게 좋나요?올려주신 google doc의 build.gradle로 진행하면, Q파일이 build/annotationProcessor아래 생성됩다... 뭐가 문제일까요? docs에 올려주신 build.gradle에서 build clean을 진행할 때 'src/main/generated'의 폴더를 삭제하는 이유가 무엇일까요? (현재 저는 Q파일을 build/ 아래 관리하고있는데, src/main/generate 아래 Q파일을 생성하시는 분들에만 해당 되는 내용인가요?)답변해주시면 감사하겠습니다!
- 미해결실전! Querydsl
compileQuerydsl없음
안녕하세요현재 프로젝트 세팅부터 막혀서 질문 드립니다.질문은 크게 3가지입니다. build 잘됨Gradle Tasks other compileQuerydsl -> 없음 ( spring boot3이상으로 되면서 안보이는게 맞나요)? build.gradleplugins { id 'java' id 'org.springframework.boot' version '3.2.0' id 'io.spring.dependency-management' version '1.1.4' } group = 'study' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' //test 롬복 사용 testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' //Querydsl 추가 implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { useJUnitPlatform() } def querydslSrcDir = 'src/main/generated' clean { delete file(querydslSrcDir) } tasks.withType(JavaCompile) { options.generatedSourceOutputDirectory = file(querydslSrcDir) } https://docs.google.com/document/d/1j0jcJ9EoXMGzwAA2H0b9TOvRtpwlxI5Dtn3sRtuXQas/edit위의 docs 설정만 build.gradle에 기입하면 src/main/generated가 생성이 안되는데 이유가 뭔가용??build폴더 말고, main/generated폴더에 Q파일이 생성되었는데 잘못 생성된건가요.? 답변 주시면 감사하겠습니다.. 추가로,, 현재 build.gradle에 $buildDir은 사용이 안된다고 하네요..!
- 해결됨실전! Querydsl
gradle .gradle폴더 차이
안녕하세요!프로젝트 세팅을 하고 test파일을 실행해보니,gradle폴더와 .gradle폴더가 생성된 것을 확인했습니다. 혹시 두개의 폴더가 무슨 차이가 있나요?out 폴더에는 무슨 폴더고 어떤것들이 들어가나요? 답변 해 주시면 감사하겠습니다!
- 미해결실전! Querydsl
DB결과가 다릅니다.
에러 없이 수행은 됩니다하지만 sql문에서는 제대로 실행되지 않고 있습니다.
- 미해결실전! Querydsl
Q hello가 작성되질 않습니다.
Q hello가 작성되질 않습니다. 다른 내용들을 참고해서 이것저것 설정을 해보았으나 외부라이브러리에 querydsl은 생성되었으나. generarted에는 큐쿼리가 생성되지 않습니다.
- 미해결실전! Querydsl
수업 예제에서 fetch join을 하지 않아도 team.name을 가져오는 이유
안녕하세요.제가 이해한 바로는, join과 fetch join의 차이가 select하는 범위의 차이라고 알고 있습니다.예를 들어,Member findMember = queryFactory .selectFrom(member) .join(member.team, team) .where(member.username.eq("member1")) .fetchOne();위 코드는 일반 join으로 team 연관관계를 조회합니다.그 결과 member 정보만 select 합니다.select m1_0.member_id, m1_0.age, m1_0.team_id, m1_0.username from member m1_0 join team t1_0 on t1_0.team_id=m1_0.team_id where m1_0.username=? 반대로 fetch join을 하면 한 번의 쿼리로 team 정보도 select문에 포함시킵니다.Member findMember = queryFactory .selectFrom(member) .join(member.team, team).fetchJoin() .where(member.username.eq("member1")) .fetchOne();select m1_0.member_id, m1_0.age, t1_0.team_id, t1_0.name, //팀 이름이 추가! m1_0.username from member m1_0 join team t1_0 on t1_0.team_id=m1_0.team_id where m1_0.username=? 여기까지 제가 이해한 게 맞다면, 질문 드립니다.강사님께서 Querydsl에서 where절 파라미터 사용하는 예제를 보여주실 때, 분명 코드는 leftJoin(), 즉 일반 join()을 사용하셨습니다.public List<MemberTeamDto> searchByWhere(MemberSearchCondition condition) { return queryFactory .select(new QMemberTeamDto ( member.id.as("memberId"), member.username, member.age, team.id.as("teamId"), team.name.as("teamName") )) .from(member) .leftJoin(member.team, team) .where( usernameEq(condition.getUsername()), teamnameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) .fetch(); }fetch join을 사용하지 않았으니 member와 연관관계를 가진 team은 프록시 객체를 가질 것입니다. 하지만 쿼리문을 보면 마치 fetch join을 한 것처럼 select 문에 team.name을 조회하는 쿼리문이 포함되어 있습니다./* select member1.id as memberId, member1.username, member1.age, team.id as teamId, team.name as teamName from Member member1 left join member1.team as team where team.name = ?1 and member1.age >= ?2 */ select m1_0.member_id, m1_0.username, m1_0.age, t1_0.team_id, t1_0.name from member m1_0 left join team t1_0 on t1_0.team_id=m1_0.team_id where t1_0.name=? and m1_0.age>=? 어째서 fetch join을 하지 않았는데 한 번의 쿼리문으로 member와 team 정보를 모두 조회할 수 있는지 궁금합니다.만약 일반 join으로 가능하다면 굳이 fetch join을 사용할 이유가 없을텐데 말입니다. 감사합니다!