묻고 답해요
160만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! Querydsl
on()구문 질문입니다
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]아래의 두 코드는 방식의 차이일뿐 같다라고 봐도 무방할까요?- 연관관계가 있는 경우from(member).join(member.team, team)- 연관관계가 없는 경우from(member).join(team).on(member.team.id.eq(team.id))
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
양방관계 편의 메소드 이름
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]set~~~ 말고 다른 이름이 좋지 않나요?
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
SpringPhysicalNamingStrategy 바뀐건가요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]찾아도 안나오는데 CamelCaseToUnderscoresNamingStrategy 이걸로 바뀐 것 같네요.
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
[정보] 카테고리
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]저도 뭐 저런 거 할 때 그냥 1depth 카테고리 테이블, 2depth 카테고리 테이블 이렇게 있는 거 아니야? 이렇게 생각했는데,요런 식으로 그러니까,아마도 내 depth, 부모 id, 그 정도로 해서 가져오는 듯 싶네요.대충 내 depth, 부모_id, 카테고리네임 이 정도로 해서한 카테고리 테이블에 다 때려넣는 식 인거 같아요. 그러니까 예를 들어 id를 통해 카테고리를 가져오게 되면,거기에 부모_id, 또 나의 자식들은 내 id를 가지고 있을 테니parent도 가져올 수 있고, child 들도 그냥 cc.parent_id = c.category_id 해서 가져올 수 있는..
-
미해결Practical Testing: 실용적인 테스트 가이드
Request Dto에서 생성자 관련...
강의에서 RequestDto를 Builder 패턴으로 생성자를 만들어주셨는데 그렇게 생성하신 이유가 있을까요?? 코드에서 확인해보면 이 생성자를 사용하지 않는걸로 확인이 되는데 .. 제가 추측하기로는 그 이후에 확장성을 위해서?,,, 라고 추측을 해봤는데 다른 이유가 있을까요????
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
h2 실행 불가 문의
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]환경: macOS Sonoma 14.1 이전 스프링 로드맵에서는 ./h2.sh하면 실행이 잘 됐는데, 이 강의부터는 터미널에 이 내용이 나옵니다.[./h2.sh: line 3: 1300 Trace/BPT trap: 5 java -cp "$dir/h2-2.2.224.jar:$H2DRIVERS:$CLASSPATH" org.h2.tools.Console "$@"]제 생각에는 스프링 로드맵 끝부분에서 자바 17을 설치하라고 해서 홈브류로 17버전 받은 다음부터 이렇게 된 것 같은데 h2를 어떻게 실행해야 할까요?오류 리포트-------------------------------------Translated Report (Full Report Below)-------------------------------------Process: java [1367]Path: /Library/Java/JavaVirtualMachines/openjdk-17.jdk/Contents/Home/bin/javaIdentifier: java Version: ???Code Type: ARM-64 (Native)Parent Process: Exited process [1365]Responsible: Terminal [631]User ID: 501Date/Time: 2023-11-05 13:53:40.4677 +0900OS Version: macOS 14.1 (23B74)Report Version: 12Time Awake Since Boot: 1500 secondsSystem Integrity Protection: enabledCrashed Thread: 0 Dispatch queue: com.apple.main-threadException Type: EXC_BREAKPOINT (SIGTRAP)Exception Codes: 0x0000000000000001, 0x000000018c3ae4e4Termination Reason: Namespace SIGNAL, Code 5 Trace/BPT trap: 5Terminating Process: exc handler [1367]Application Specific Information:References to Carbon menus are disallowed with AppKit menu system (see rdar://101002625). Use instances of NSMenu and NSMenuItem directly instead.
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
이번 예제에서 Integer말고 int로 엔티티의 필드가 작성된 이유가 있을까요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]Integer가 객체라 null이나 그런 것에서 비교적 안전하다고 배웠었던거 같은데.. int로 한 이유가 있을까요?
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
7강 수강 중 똑같이 따라했는데 경고가 떠요
어떻게하면 위 경고문구를 없앨 수 있는 지 궁금합니다.인텔리제이 2023버전을 쓰는 것도 문제가 될까요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
정적 팩토리 메서드에 static이 붙어야 하는 이유
안녕하세요. 강의 잘 보고 있습니다! 다른 분 질문에 궁금한 것이 해소되지 않아 질문드립니다.https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&unitId=24297&category=questionDetail&q=30892&tab=community정적 팩토리 메소드 사용 이유중에 static 메모리에 올라가기때문에 새로운 객체를 생성하지 않는 장점이 있는 거라고 알고 있습니다. 이 예제의 경우 static을 빼도 JPA가 엔티티로 관리하면서 어차피 사용할 수 있는 부분아닌가요..? 라는 질문에서 강사님께서는 static을 빼보면 이해될 거라고 답변하셨습니다.제가 생각하기로는 정적 팩토리 메서드 안에서 생성자를 통해 인스턴스를 생성하는 것은 똑같아 보이고, 호출할 때 new가 아닌 Order.createOrder()로 호출하는 것 외에는 차이점을 못 느꼈습니다.조금 더 상세한 가르침을 주시면 감사하겠습니다!
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
@Runwith 어노테이션 오류.. Junit4
강의대로 테스트 클래스 작성중인데.. @RunWith 어노테이션 자동완성이 안되더라구요..? 그래서 코드 복붙 했는데도 저렇게 뜹니다..junit4로 강의랑 똑같이 설정해서 작성했는데 뭐가 문제일까요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
dtype 조회 방법
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 다른 객체들은 id로 조회가 가능한데 dtype같은 경우는 조회를 어떻게 하는 건가요?
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
커맨드성
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]선생님들.8:20 이 때 쿼리랑 커맨드 성을 분리하라. 이 말이트랜잭션 때문에 그런건가요? 그러니까 쿼리는 날리면 바로 flush가 되기 때문에 상관없는데, 말하시는 사이드이펙트라는게 그 영속성 컨텍스트 안에 있다가 트랜잭션 commit 시점에 모든 것이 다 DB로 날라가니까,Member를 직접 주게 되면 주소를 주는 것 이기때문에, 영속성 컨텍스트에도 영향을 미쳐, 뭔가 저렇게 save 해놓고 그 member를 직접 다루게 되면 save하는 그 값에도 영향이 있을 수 있기 때문에 그런건가요?
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
지금도 application.yml 쓰는 추세인가요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]물론 회사마다 다를 것 같기는 한데..application.properties가 기본이니 뭔가 보통 이렇게 default로 하는 건 잘 맞기 때문에 두는 것 같아서.. 하긴 뭔가 설정을 표현하는 하나의 방법일 뿐이라 상관없는건가..강의가 이제 그래도 몇년 전꺼라 좀 바뀐 점이 있을까 해서요.
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
@ColumnTransformer 사용시 문의사항
안녕하세요. DB는 PostgreSql로 DB암호화로 특정 컬럼 데이터가 암호화된 상태입니다.구글링을 해보니 @ColumnTransformer를 통해 암/복호화가 가능하다고해서정상적으로 확인했습니다.다만, read,write프로퍼티내에 보안에 민감한 key값이 직접적으로 들어가서application파일 따로 두고싶은데 방법이 있을까요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
스프링부트 3.15버전인데 junit5 vs junit4 어떤 걸 사용해야 하나요?
스프링부트 3.15버전 사용하고 있는데강의 자료에 나와있는 것처럼 build.gradle에 내용을 추가해서 junit4를 사용하는 게 좋을까요?추가하지 않으면 junit5로 동작하는 건가요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
Kafka connector
window 사용 중이고, 서버는 전부 열려있습니다만, POSTMAN에서 JSON 형식으로 { "name" : "my-source-connect", "config" : { "connector.class" : "io.confluent.connect.jdbc.JdbcSourceConnector", "connection.url":"jdbc:mysql://localhost:3306/mydb", "connection.user":"root", "connection.password":"test1357", "mode": "incrementing", "incrementing.column.name" : "id", "table.whitelist":"users", "topic.prefix" : "my_topic_", "tasks.max" : "1" }를 보냈을 때 뜨는 오류가 "error_code": 500, "message": "Failed to find any class that implements Connector and which name matches io.confluent.connect.jdbc.JdbcSourceConnector, available connectors are: PluginDesc{klass=class org.apache.kafka.connect.file.FileStreamSinkConnector, name='org.apache.kafka.connect.file.FileStreamSinkConnector', version='3.6.0', encodedVersion=3.6.0, type=sink, typeName='sink', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.file.FileStreamSourceConnector, name='org.apache.kafka.connect.file.FileStreamSourceConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.mirror.MirrorCheckpointConnector, name='org.apache.kafka.connect.mirror.MirrorCheckpointConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.mirror.MirrorHeartbeatConnector, name='org.apache.kafka.connect.mirror.MirrorHeartbeatConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.mirror.MirrorSourceConnector, name='org.apache.kafka.connect.mirror.MirrorSourceConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}" 입니다. 어떤오류인지 잘 모르겠네요
-
미해결
Spring JPA 다대다 관계 테이블 질문드립니다.
2개의 다대다 관계의 엔티티를 연결테이블을 이용해서 총 3개의 엔티티로 구성했습니다.Companion 엔티티@Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder @AllArgsConstructor public class Companion { // Companion 엔티티는 동반 여행에 참여하는 개별 동반자의 정보를 나타냅니다. // 회원들과의 다대다 관계를 통해 여러 회원과 연결될 수 있으며, 동반 여행의 세부 정보를 저장합니다. @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 동반자 엔티티의 고유 식별자 private String name; // 동반자의 이름 private String code; // 동반자를 구분하는 코드 정보 private Boolean isStart; // 동반 여행의 시작 여부 private Boolean isEnd; // 동반 여행의 종료 여부 private String tendency; // 동반자의 특성 정보 private String mate; // 동반자에 대한 추가 정보 private LocalDateTime startTime; // 동반 여행의 시작 시간 private LocalDateTime endTime; // 동반 여행의 종료 시간 @OneToMany(mappedBy = "companion") private List<MemberCompanion> companionMembers = new ArrayList<>(); }Member 엔티티@Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class Member { // Member 엔티티는 시스템 사용자를 나타내며, 동반자와의 다대다 관계를 허용합니다. // 각 회원은 여러 개의 동반자와 연결될 수 있으며, 동반자 정보를 관리합니다. @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 회원 엔티티의 고유 식별자 @Column(unique = true) private String email; // 회원의 고유한 이메일 주소 private String nickname; // 회원의 사용자명 private String snsType; // 소셜 로그인 종류 private String originalId; // 고유 아이디 @OneToMany(mappedBy = "member") private List<MemberCompanion> myCompanions = new ArrayList<>(); }Companion과 Member를 연결하는 Entity@Entity @Getter @Builder @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor public class MemberCompanion { // MemberCompanion 엔티티는 회원과 동반자 간의 관계를 정의하는 엔티티입니다. // 회원과 동반자를 관련시키며, 회원-동반자 관계의 역할 정보를 포함합니다. @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 회원-동반자 관계 엔티티의 고유 식별자 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; // 회원 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "companion_id") private Companion companion; // 그룹 @Enumerated(value = EnumType.STRING) private Role role; // 회원-동반자 관계 역할 (예: 리더, 구성원) } 문제가 발생한 것은, Companion을 build하고 생성된 Companion과 Member를 통해서 MemberCompanion을 생성한 후, 생성된 MemberCompanion을 다시 Companion의 companionMembers에 넣으려고하면 순환 참조 오류가 터집니다. Dto를 사용해봤지만 잘 해결이 안되어서 질문 올립니다./** * 그룹 등록을 처리하는 메서드 * * @param companionAddRequestDto 그룹 등록 요청 DTO * @return ResponseEntity 객체를 반환하여 등록 성공 또는 실패 응답을 전송 */ @Transactional public ResponseEntity registCompanion(CompanionAddRequestDto companionAddRequestDto) { Member member = memberRepository.findById(companionAddRequestDto.getMemberId()) .orElseThrow(() -> new EntityNotFoundException("회원을 찾을 수 없습니다.")); Companion companion = Companion.builder() .name(companionAddRequestDto.getName()) .code(generateRandomCode()) .isStart(companionAddRequestDto.getIsStart()) .isEnd(companionAddRequestDto.getIsEnd()) .tendency(companionAddRequestDto.getTendency()) .mate(companionAddRequestDto.getMate()) .startTime(companionAddRequestDto.getStartTime()) .endTime(companionAddRequestDto.getEndTime()) .companionMembers(new ArrayList<>()) .build(); companionRepository.save(companion); // MemberCompanion 엔티티를 생성 MemberCompanion memberCompanion = MemberCompanion.builder() .member(member) .companion(companion) .role(Role.LEADER) .build(); memberCompanionRepository.save(memberCompanion); // 등록 성공 응답을 생성하고 반환 return ResponseEntity.ok() .body(new SuccessResponseDto(true, "그룹 등록이 완료되었습니다.", companion)); }해당 코드에서memberCompanionRepository.save(memberCompanion);이후 companion.getCompanionMembers.add(memberCompanion)시 getCompanionMembers값이 null이라 나오고 다른 방법을 사용하면 순환참조가 발생합니다.해결 방법이 있을까요..?
-
미해결호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
검증시 변수값이 아니라 상수값으로 비교하는 이유가 있나요?
문제가 발생한건 아니고 단순 질문입니다! 테스트 짜실 때 보면,// 1번 코드 assertEquals("상수값", findComment.getAuthor()); // 2번 코드 assertEquals(addComment.getAuthor, findComment.getAuthor());1번처럼 상수값과 비교를 많이 하시는데, 저는 2번처럼 저장하려고 했던 객체와 실제로 저장된 후 리턴된 객체간의 비교를 많이 하는 것 같습니다. 2번이 유지보수의 측면으로 봤을 때 더 좋아보이는데 1번 스타일로 테스트 코드를 짜는 이유가 있으실까요?
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
List<Domain> -> List<Response> 변환을 Controller에서 하는 게 맞나요?
Domain -> Response 변환 코드를 Domain에 정의해두고Controller에서 Domain 메서드를 호출해서 Response를 변환하는게 맞나는 건 이해했습니다. 근데 실제 API에 해당 내용을 적용하려고 보니 Domain 단 건 조회보다는 List<Domain>을 반환하는 경우가 훨씬 많았습니다. 따라서 List<Domain>을 List<Response>로 변환해야 하는데 해당 작업을 for문이나 Stream으로 Controller로 처리하려니 Controller 코드도 지저분해지고 Controller가 하는 역할에 부합하지 않게 되는 것 같습니다.List<>를 변환할 때는 어디서 하는게 올바른 것인지 질문드립니다!
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
계층형 테이블에 매핑된 엔티티 컬렉션을 fetch join으로 가져올 때 쿼리 개수 질문드립니다.
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]코드는 마지막에 첨부되어 있습니다.게시글 엔티티 Post가 게시글에 달린 댓글 엔티티 Comment의 컬렉션을 프로퍼티로 갖습니다. 댓글 엔티티 Comment는 COMMENT, REPLY 타입으로 구분되며, 프로퍼티로 parentComment와 replies를 갖습니다. COMMENT 타입은 parentComment == null이며, REPLY 타입은 replies == null입니다. Post.comments는 Comment의 타입을 고려하지 않고 모두 갖고 있습니다.위 상황에서 게시물 상세 정보를 가져올 때, comments를 fetch join하여 Post 엔티티를 가져와 댓글들을 타입 계층에 따라 분리하는 작업을 합니다. 이때 REPLY 타입의 replies에 접근하지 않습니다. COMMENT 타입의 replies에는 접근하며 해당 replies는 모두 같은 게시물에 속해있습니다.예상했던 쿼리의 개수는 1 혹은 (1 + COMMENT 타입 수) 였습니다. 애초에 둘 중 무엇인지 궁금해서 일을 진행했었습니다.테스트를 실행해보면, post.comments의 요소에 처음 접근할 때 REPLY 타입을 가져오는 쿼리가 발생합니다. 그런데 이후에 다른 COMMENT 타입에 접근할 때는 REPLY를 가져오는 쿼리가 발생하지 않습니다. 만약 Post의 COMMENT 타입 Comment.replies에 접근할 때 쿼리가 발생하는 거라면 COMMENT 타입의 개수만큼 발생해야 하는게 아닌가요? 제가 무엇을 놓치고 있는지 궁금합니다.논외로, Spring data JPA의 @Query를 통해 작성한 단순한 정적 쿼리가 계속 반복된다면 QeuryDSL로 옮겨 반복을 줄이는게 합리적일까요? 아니면 spring data jpa의 편리함을 유지하는게 합리적인가요? 감사합니다.public class Post extends BaseEntity { public static final int TITLE_MAX_LENGTH = 50; public static final int CONTENT_MAX_LENGTH = 10000; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "post_id", nullable = false, updatable = false) private Long id; @Column(name = "title", nullable = false, length = TITLE_MAX_LENGTH, updatable = false) private String title; @Column(name = "content", nullable = false, length = CONTENT_MAX_LENGTH) private String content; @Column(name = "hits", nullable = false) private Long hits; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "writer_id") private Member writer; @OneToMany(fetch = FetchType.LAZY, mappedBy = "post", orphanRemoval = true) @ToString.Exclude private List<Comment> comments = new ArrayList<>(); }public class Comment extends BaseDeleteEntity { public static final int CONTENT_LENGTH = 1000; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "comment_id", nullable = false) private Long id; @Column(name = "content", nullable = false, length = CONTENT_LENGTH) private String content; @Column(name = "type", nullable = false, updatable = false) private CommentType type; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id", nullable = false, updatable = false) private Post post; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "writer") private Member writer; @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true) private List<Comment> replies = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_comment_id", updatable = false) @ToString.Exclude private Comment parent; } @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile LEFT JOIN FETCH p.comments WHERE p.id = :postId """ ) Optional<Post> findPostById(@Param("postId") final Long id); @Autowired EntityManager em; @Autowired PostRepository postRepository; @Test public void 정상작동테스트_추가적인_쿼리_발생_x() throws Exception { //given Post post = PostTest.create("username", "nickname"); Member member = post.getWriter(); em.persist(member); em.persist(post); Long postId = post.getId(); int commentCount = 10; int replyCount = 2; createComment(post, member, commentCount, replyCount); em.flush(); em.clear(); //when int totalCommentCount = commentCount * (replyCount + 1); Post findPost = postRepository.findPostById(postId).get(); System.out.println(); //then for (Comment comment : findPost.getComments().stream().filter(Comment::isCommentType).toList()) { // 이때 REPLY 타입을 조회하는 쿼리가 1회 발 System.out.println("comment = " + comment); System.out.println("comment.getReplies().get(0) = " + comment.getReplies().get(0)); } Assertions.assertThat(post.getComments().size()).isEqualTo(totalCommentCount); }[테스트 실행 시 쿼리]2023-11-02T11:45:00.154+09:00 DEBUG 51844 --- [ main] org.hibernate.SQL :selectp1_0.post_id,c1_0.post_id,c1_0.comment_id,c1_0.content,c1_0.created_by,c1_0.created_date,c1_0.deleted_date,c1_0.is_deleted,c1_0.last_modified_by,c1_0.last_modified_date,c1_0.parent_comment_id,c1_0.type,c1_0.writer,p1_0.content,p1_0.created_by,p1_0.created_date,p1_0.hits,p1_0.last_modified_by,p1_0.last_modified_date,p1_0.title,w1_0.member_id,w1_0.created_by,w1_0.created_date,w1_0.last_modified_by,w1_0.last_modified_date,w1_0.login_id,w1_0.login_type,w1_0.profile_id,p2_0.profile_id,p2_0.created_by,p2_0.created_date,p2_0.last_modified_by,p2_0.last_modified_date,p2_0.nickname,w1_0.user_role,w1_0.social_login_id,w1_0.usernamefrompost p1_0left joinmember w1_0on w1_0.member_id=p1_0.writer_idleft joinprofile p2_0on p2_0.profile_id=w1_0.profile_idleft joincomment c1_0on p1_0.post_id=c1_0.post_idwherep1_0.post_id=?2023-11-02T11:45:00.162+09:00 INFO 51844 --- [ main] p6spy2023-11-02T11:45:01.221+09:00 DEBUG 51844 --- [ main] org.hibernate.SQL :selectr1_0.parent_comment_id,r1_0.comment_id,r1_0.content,r1_0.created_by,r1_0.created_date,r1_0.deleted_date,r1_0.is_deleted,r1_0.last_modified_by,r1_0.last_modified_date,r1_0.post_id,r1_0.type,r1_0.writerfromcomment r1_0wherearray_contains(?,r1_0.parent_comment_id)[반복되는 spring data jpa 쿼리]public interface PostRepository extends JpaRepository<Post, Long>, PostQueryRepository { /** * Post 반환 시 Member, Profile을 fetch join한다. ~ToOne 매핑관계에 대한 fetch join은 별명을 사용할 수 있고, 연계하여 fetch * join할 수 있다. * * @param id must not be {@literal null}. * @return */ @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile LEFT JOIN FETCH p.comments WHERE p.id = :postId """ ) Optional<Post> findPostById(@Param("postId") final Long id); /** * Post를 페이징 처리하여 Page<Post>로 반환한다. 이때 Member와 Profile을 fetch join한다. * * @param pageable the pageable to request a paged result, * can be {@link Pageable#unpaged()}, * must not be {@literal null}. */ @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile """, countQuery = "SELECT count(p) FROM Post p" ) @Override Page<Post> findAll(final Pageable pageable); /** * WriterId가 memberId와 같은 Post를 페이징 처리하여 Page<Post>로 반환한다. 이때 Member와 Profile을 fetch join한다. * * @param writerId writerId가 일치하는 Post들을 반환한다. * @param pageable 페이징 정보 */ @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile WHERE w.id = :writerId """, countQuery = "SELECT count(p) FROM Post p WHERE p.writer.id = :writerId" ) Page<Post> findAllByWriterId(@Param("writerId") final Long writerId, final Pageable pageable); }