묻고 답해요
138만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
연관관계 편의 메소드 관련 질문
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요, 스프링부트 강의 잘 수강하고 있습니다.<엔티티 설계시 주의점> 23:25 강의를 수강하며, 궁금한 점이 두가지 있습니다!메소드 내 순서//연관관계 편의 매소드 public void setMember1(Member member){ this.member = member; member.getOrders().add(this); } //순서 이렇게도 가능한가? public void setMember2(Member member){ member.getOrders().add(this); this.member = member; }강의에서는 setMember1 방식으로 진행을 하였는데,만약 setMember2 방식으로 순서가 바뀌어도 문제가 없는 것인지 궁금합니다.@Setter <-> setMember() 메소드 중복 Order 엔티티에 @setter 어노테이션을 붙였기에, 롬복이 setMember() 메소드를 자동으로 생성해줍니다.강의에서는 연관관계 메소드명을 setMember()으로 생성하여, 롬복이 만들어주는 메소드랑 이름이 중복됩니다. 생성자를 만들어 실험해보니 롬복이 만들어주는 setMember() 메소드는 보여지지 않으며 연관관계 편의 메소드가 덮어쓰기 한 것으로 보이는데, 제대로 한 것이 맞을까요?만약 실무에서 롬복을 사용할 때, 이런 경우(연관관계 메소드가 롬복 @Setter 메소드 위에 덮어쓴 경우)가 된다면 문제가 될 수도 있을까요???
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
JPA FULL TEXT SEARCH 에 관한 질문입니다.
영한님, 안녕하세요.본 강의와 맞지 않는 질문을 드린거 같아서 죄송합니다. 현재 댓글을 검색하는 기능에 기존 LIKE %keyword% 를 쓰는 방식보다 full text search를 적용하는 것이 성능 상 이점이 있다고 하여 적용 중에 있습니다. 하지만, 기존 like 방식보다 오히려 성능이 안나오는 현상을 겪고 있습니다. 10만 건을 테스트를 해보았고 100만건 1만건 5만건 모두 성능이 비등하거나 떨어집니다.. ex) 10만건 성능like = 429msfull text search = 476ms 의 성능을 보이고 있습니다. 뭔가 놓치고 있는 것 같습니다. 그런데 mysql 실행계획으로 퍼포먼스를 확인해보니, full text search가 2배 빠른 것으로 확인이 되는데 이게 왜 그런 건지 모르겠습니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
업데이트 계획 및 커뮤니티 질문 답변 문의
강의 소개에Spring Boot 3.2 + Spring Cloud 2023.0.0업데이트 예정 (2024-01)쓰여 있는데 해당 계획 실습 예제 파일만 업데이트 하시는 건지 혹은 강의 녹화본까지 업데이트를 하시는 것이 유효한지 질문드립니다.또한 커뮤니티의 질문 글을 보면 강사님께서 8월 이후로 답변이 드문드문 한 상태인데인프런 AI의 답변이해당 질문 글에 대한 적절한 답변을 하여 답변을 하시지 않는 건지, 현재 바쁘셔서 답변을 하시지 않는 건지 질문드립니다. 감사합니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
강의 정리 포스팅
혹시 복습차원에서 강의 정리해서 개인블로그에 포스팅 해도 될까요??만일 포스팅하게 된다면, 꼭 출처는 남기겠습니다!!
-
미해결실전! 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이거를 사용한 이유가 유지보수와 가독성을 더 높여주는 방법이라 이해했는데 맞나요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
apigateway-service 톰캣 서버로 실행시 안되는 이유
실수로 gateway-mvc라이브러리를 설치 후 실행 했을 때 톰캣으로 실행이 되었는데 이때는 게이트웨이url에 /first-service/welcome를 붙였을 때 실행이 되지 않았는데,gateway 라이브러리 다시 설치후 netty 에서는 정상적으로 동작 하는데 톰캣에서는 안되고, netty에서만 되는 이유가 뭔가요???
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
생성 메서드를 작성하는 클래스의 기준과 클라이언트에서 엔티티 아니라 orderid를 넘기는 이유
안녕하세요 ? 강의를 들으면서 몇가지 질문이 있어 글을 씁니다. 생성 메서드를 사용하는 이유에 대한 다른 질문의 답변의 외부 글을 읽었습니다. 강의의 Order와 OrderItem의 경우엔 생성자를 쓰던 생성 메서드를 쓰던 별 차이가 없는 것 같다고 생각이 듭니다. 혹시 이 두 클래서에서 생성 메서드를 사용했을 때 얻는 이점을 자세히 설명해주실 수 있나요? 특정한 클래스에만 생성 메서드를 쓰는 기준이 궁금합니다. 멤버변수 중에 객체가 있는 경우인가요?주문 삭제의 경우 클라이언트에서 orderId를 넘깁니다. 그럼 영속성 컨텍스트에서 한번 조회하여 order 엔티티를 찾습니다. 클라이언트에도 Order 엔티티 정보가 그대로 있어서 넘기면 바로 쓸 수 있는데 id를 넘기는 이유가 궁금합니다. 제가 생각해본 바로는 영속성 컨텍스트(캐시)에서 조회하는 건 자원이 크게 들지 않고 객체가 큰 경우에는 객체 자체를 넘기는 게 오히려 더 큰 자원이 들 수 있다는 점입니다. 맞을까요?답변 기다리겠습니다. 감사합니다 !
-
미해결실전! 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를 사용하면 되는건가요? 아니면 다른 방법이 있나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
LocalDateTime.now() 를 검증하는 테스트에 관하여
안녕하세요.LocalDateTime.now() 를 이용하여 테스트 하는 걸 지양하자라는 강사님 강의 중에 제가 겪었던 경험에 대해 질문이 있습니다.현재 개인적인 프로젝트에서 테스트 코드를 작성하고 있습니다.회원가입을 할때 가입한 시점을 저장하기 위해 LocalDateTime 을 파라미터로 받고 있는데요, 이 로직을 검증하기 위해 테스트 코드를 작성하였습니다.그러나 아래와 같은 에러가 발생하더라구요org.opentest4j.AssertionFailedError: expected: 2024-01-03T21:25:11.333406800 (java.time.LocalDateTime) but was: 2024-01-03T21:25:11.333407 (java.time.LocalDateTime) when comparing values using 'ChronoLocalDateTime.timeLineOrder()' Expected :2024-01-03T21:25:11.333406800 (java.time.LocalDateTime) Actual :2024-01-03T21:25:11.333407 (java.time.LocalDateTime)대략 에러 메세지를 확인해보니 LocalDateTime.now 로 생성한 값과 저장한 뒤에 조회한 시간의 오차가 발생하여 테스트가 실행하는 것 이었습니다.해서 LocalDateTime.now 가 아닌 임의의 시점을 지정해주어서 문제를 해결할 수 있었는데혹시 비슷한 경험을 하신적이 있으신지?있으시다면 제가 말한 방법대로 테스트 코드를 작성하는게 적절한지? 가 궁금합니다!감사합니다!!
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
회원가입 테스트 코드 관련해 질문 드립니다..ㅠ
7분 08초 정도에 회원가입 테스트 코드를 실행하는 부분에서요,,! MemberServiceTest 클래스에 @Transactional을 걸어서회원가입() 메소드를 하나의 트랜잭션으로 관리하는 것으로 이해했습니다... memberService.join(member); 으로 persistence context에 member를 영속화 시킨 상태인데,그 다음 코드인 memberRepository.findOne(savedId);에서, 왜 select 문이 나가는거죠?하나의 트랜잭션이고, member가 영속 상태라서 1차 캐시에서 관리되면, context에서 가져와야 하는게 아닌가요?... 트랜잭션을 롤백한다는 것의 의미가 DB의 트랜잭션을 롤백한다는 것 뿐만 아닌 영속성 컨텍스트도 롤백하는 것을 의미하나요?.. 만약 그렇다면, 저는 @Transactional을 메소드 단위로 걸었는데, 왜 회원가입() 메소드 중간에 롤백이 되는 건가요?..ㅠ아예 애초부터 스프링에서의 트랜잭션 롤백은 영속성 컨텍스트에 반영조차 하지 않는 거라고 봐야 하나요?.. 너무 헷갈립니다 ㅠ.ㅠ
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
JUnit5 @Test(expected = ) 미지원하는데 테스트 코드 작성 방법
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.JUnit5를 사용하여 테스트 코드를 작성 중인데, JUnit4와는 다르게 @Test(expected = ) 사용이 불가하더라고요.다음과 같이 테스트 코드를 작성하면 될까요? @Test public void 중복_회원_예약() throws Exception { //given Member member1 = new Member(); member1.setName("kim"); Member member2 = new Member(); member2.setName("kim"); //when memberService.join(member1); assertThrows(IllegalStateException.class, () -> { memberService.join(member2); }); }
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
OrderApiController
웹 개발 구조에서backend - repository - service - controller - frontend이런 식으로 이해를 하고 있었습니다. 강의를 수강 중 MemberApiController는 MemberService를 의존관계 주입을 받아 Api테스트를 진행하는데 OrderApiController는 왜 Service를 주입받지 않고 Repository를 주입받아 사용하는지 궁금합니다.
-
해결됨실전! 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 방식으로 페이징 처리를 할 때 이렇게 하는지 아니면 다른 방식이 더있는지 궁금해서 질문드립니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
섹션 5-2. JPA에서 em.createQuery(SQL) 질문
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. JPA에서 em.createQuery("select i from Item i", ...)을 쓰시는데 저 구문을 h2에 직접 날려보면 i 를 찾을 수 없다고 나오는데 select * from item을 안 쓰는 이유가 있나요?
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
em.detach 메서드와 관련하여 질문드립니다.
위의 로직을 보시면Member 객체를 생성후 persist() 메서드를 호출한 후에 detach() 메서드를 호출하였습니다.그러면 제 생각에는 persist()를 호출한 순간에 쓰기지연 SQL저장소에 insert문이 있기 때문에 flush하면 insert 쿼리가 console에 나와야하는데 왜 안나오는지 이유를 모르겠습니다.혹시 detach를하면 쓰기 지연 SQL저장소에 있는 insert가 사라지는건가요?
-
해결됨스프링 DB 2편 - 데이터 접근 활용 기술
물리적인 트랜잭션을 처리하는 부분에 대해 헷갈리는 점이 있습니다.
안녕하세요.항상 강의잘 듣고있습니다!강의를 완강하고 복기하던 중에 궁금한 점이 있습니다. 강의에서는 커밋 또는 롤백을 하는 경우 트랜잭션 메니저로 요청을 보내 물리적인 부분을 처리한다 라고 학습했는데요, 가끔씩 강의를 듣다보면 트랜잭션 매니저 -> 트랜잭션 동기화 매니저 를 통해 물리적인 트랜잭션을 처리한다고 말씀하셔서 (스프링 트랜잭션 전파5 - 외부 롤백 6분 20초경)마지막 물리적으로 처리하는 부분이 트랜잭션 매니저단에서 끝나는지, 트랜잭션 매니저에서 트랜잭션 동기화 매니저를 통해 끝나는지 헷갈려서 질문남깁니다.! 감사합니다!!
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
@Transactional 과 entityManager 범위에 관해 질문 드립니다.
안녕하세요! 강의 수강 중 질문이 생겨 작성합니다.. 답변 부탁드립니다..!ㅠ.ㅠ -- MemberRepository 입니다. ( @Transactional 키워드 모두 지움 )@Repository public class MemberRepository { @PersistenceContext EntityManager em; public Member findMember( Long id ) { Member member = em.find(Member.class, id); return member; } public Member findMember2( Long id ) { Member member = em.find(Member.class, id); return member; } } -- 위 레파지토리를 호출하는 테스트 코드 입니다. ( 데이터는 미리 넣어둔 상태 입니다. )@SpringBootTest class MemberRepositoryTest { @Autowired MemberRepository mRepo; @Test public void test2() { System.out.println("================================================="); System.out.println(mRepo.getClass()); //when Member found1 = mRepo.findMember(1L); Member found2 = mRepo.findMember2(1L); System.out.println("================================================="); //then assertEquals(found2, found1); } } 1 ) 테스트 코드에서 주입받은 mRepo의 클래스 타입을 확인하면 CGLIB로 생성한 프록시 클래스가 출력됩니다. 위 코드에서 보는 바와 같이 @Transactional 어노테이션은 존재하지 않는데도 실 클래스가 아닌 프록시 객체로 만들어지는 이유가 뭔가요?? 2 ) 테스트 코드에서 test2()를 실행하면 select를 2번 실행합니다. test2()에 @Transactional을 걸면 -> select 1번 -> 너무 당연한데MemberRepo 클래스 레벨에서 @Transactional -> 테스트 코드 실행 시 select 2번MemberRepo 클래스 각 메소드에 @Transactional -> 테스트 코드 실행 시 select 2번 2-1) 위와 같은 결과가 발생하는 이유를 모르겠습니다.. @Transactional을 어떻게 걸든 한 메소드가 종료하고 나면 PersistenceContext 가 닫히는 건가요?.. 2-2) MemberRepo 에서 주입받은 entityManager는 proxy 클래스가 맞는거죠..?@Transactional을 표기해주지 않아도, em을 사용하는 메소드가 호출되고 종료 될 때마다, 매번 proxy 객체 내부의 실제 entityManager 객체가 변경되는 건가요?? 2-3) em.find(Member.class, 1L) 같은 조회성 질의에서@Transactional(readOnly=true) 와 아예 해당 어노테이션을 사용하지 않았을 경우의 결과가 동일하였습니다. 그렇다면 readOnly를 사용하는 이유가 대체 무엇인가요?.. 질문이 많아서 죄송합니다 ㅠㅠ 머리에 정리가 되질 않아서요..답변 부탁드립니다!!
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
Transaction과 영속성 컨텍스트
안녕하세요. 강의 전반을 보고 영속성 컨텍스트에 대한 궁금증이 있어 이것 저것 테스트 해보다가 질문 남깁니다.기본적으로 Spring Data JPA에서 제공하는 영속성 컨텍스트는 1.Transaction 단위로 생성되는 것으로 이해하고 있었습니다.또한, 서로 다른 영속성 컨텍스트에 대해서 1차 캐시를 공유하지 않는것으로 알 고 있어, 서로 다른 2개의 @transaction에 대해서는 같은 값을 조회하더라도 동등성을 보장하지 않을것이라고 생각합니다.먼저, 위에 대한 제 생각이 맞는지 궁금합니다. 두 번째로 제가 직접 Application을 띄우고 하나의 http 요청에 대해 서로 다른 2개의 @transaction을 처리하는 로직을 넣고 서로 다른 @transaction에서 같은 값을 조회 후 동등성을 테스트 해봤는데 동등하다는 결과가 반복되었습니다. 이에 혹시 위에서 언급한 1.Transaction이 http 요청 단위의 transaction을 의미하는 것인가 라는 궁금증이 들었습니다. 위 2개 질문 이외 혹시 제가 놓치거나 잘못 이해한 부분이 있다면 알려주시길 바랍니다.감사합니다.
-
해결됨실전! 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