묻고 답해요
138만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨실전! Querydsl
PageableExecutionUtils.getPage 의 최적화 로직만 따로 뺀 count 메소드입니다.
갱신된 강의자료에도 나와 있듯이, fetchResult, fetchCount 등의 메소드는 deprecated 예정입니다. 또한 해당 메소드는 Page 타입을 반환하기 때문에 Page 타입을 직접적으로 조작해야 한다는 제약이 있습니다. 이걸 잘 쓰면 되는데, Page 의 시작번호 등 제약도 있어서 저는 회사에서 Page 를 따로 만들어서 사용하거든요. 그래서 search 메소드와 count 메소드는 항상 분리하고 있습니다.2. 그런 점에서 count 만 최적화하는 로직만 베껴서 만든 최적화 메소드입니다.실제로 count 구문을 실행하기 위한 메소드입니다. 필요에 따라서 public 으로 만들어도 될 것 같네요. // count 실행 메소드 private Long countByCond(MemberSearchCond cond) { return query .select(member.countDistinct()) .from(member) .where( usernameEq(cond.getUsername()), teamNameEq(cond.getTeamName()), ageGoe(cond.getAgeGoe()), ageLoe(cond.getAgeLoe()) ) .fetchOne(); } 최적화 로직을 베낀 메소드입니다. 스프링 데이터 JPA 는 아래 코드와 같이 page, size 값이 null 이 아니고, 적절한 범위에 있는지를 판단하여 isPaged 라는 분기를 만듭니다. 참고로 코드를 분석하면서 내린 뇌피셜이니 혹시 틀리다면 말씀해주세요. page 가 0 이 아니라 1 부터 시작하는 건 제 취향입니다. 회사에서 그렇게 쓰고 있거든요. 필요에 따라 0 부터 시작하도록 바꾸는 건 어렵지 않을 것 같습니다.public Long countByCondOptimization(List<?> content, MemberSearchCond cond, Long page, Long size) { boolean isPaged = page != null && size != null && page > 0 && size > 0; long offset = isPaged ? (page - 1) * size : 0L; long contentSize = content.size(); if (!isPaged || offset == 0) { if (!isPaged || size > contentSize) { return contentSize; } return this.countByCond(cond); } if (contentSize != 0 && size > contentSize) { return offset + contentSize; } return this.countByCond(cond); } 이렇게 하여 count 쿼리를 리스트 조회 쿼리와 분리하여 재사용성을 높이면서, 성능도 최적화할 수 있습니다.감사합니다.
-
해결됨실전! Querydsl
P6Spy 포맷 설정 공유합니다.
jpa 로그의 포맷처럼 줄바꿈을 넣으면서, 동시에 ? 자리에 바인딩 파라미터를 넣는 코드입니다. P6Spy 1.8.1, Spring Boot 2.7.x, 마리아DB 10.1.x 사용했습니다.application.properties 에서 JDBC 드라이버나 URL 에 P6Spy 를 추가하면 로그가 2번 출력될 수 있으니 주의하세요. 아래처럼 P6Spy 관련 드라이버? 코드? 가 포함되지 않도록 하시면 됩니다.# datasource spring.datasource.url=jdbc:mariadb://localhost:3306/YOUR_SCHEMA spring.datasource.username=YOUR_USERNAME spring.datasource.password=YOUR_PASSWORD # p6spy log logging.level.p6spy=info decorator.datasource.p6spy.enable-logging=true 로그의 포맷을 실질적으로 꾸미는 구현 클래스입니다. 추가 정보는 실행시간 제외하고는 뺐는데, 필요하시면 추가하시면 됩니다. import com.p6spy.engine.logging.Category; import com.p6spy.engine.spy.appender.MessageFormattingStrategy; import org.hibernate.engine.jdbc.internal.FormatStyle; import java.util.Locale; public class P6SpyFormatter implements MessageFormattingStrategy { private static final String NEW_LINE = "\n"; private static final String TAP = "\t"; private static final String CREATE = "create"; private static final String ALTER = "alter"; private static final String DROP = "drop"; private static final String COMMENT = "comment"; @Override public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) { if (sql.trim().isEmpty()) { return formatByCommand(category); } return formatBySql(sql, category) + getAdditionalMessages(elapsed); } private static String formatByCommand(String category) { return NEW_LINE + "Execute Command : " + NEW_LINE + NEW_LINE + TAP + category + NEW_LINE + NEW_LINE + "----------------------------------------------------------------------------------------------------"; } private String formatBySql(String sql, String category) { if (isStatementDDL(sql, category)) { return NEW_LINE + "Execute DDL : " + NEW_LINE + FormatStyle.DDL .getFormatter() .format(sql); } return NEW_LINE + "Execute DML : " + NEW_LINE + FormatStyle.BASIC .getFormatter() .format(sql); } private String getAdditionalMessages(long elapsed) { return NEW_LINE + NEW_LINE + String.format("Execution Time: %s ms", elapsed) + NEW_LINE + "----------------------------------------------------------------------------------------------------"; } private boolean isStatementDDL(String sql, String category) { return isStatement(category) && isDDL(sql.trim().toLowerCase(Locale.ROOT)); } private boolean isStatement(String category) { return Category.STATEMENT.getName().equals(category); } private boolean isDDL(String lowerSql) { return lowerSql.startsWith(CREATE) || lowerSql.startsWith(ALTER) || lowerSql.startsWith(DROP) || lowerSql.startsWith(COMMENT); } } 5. 아래 코드가 중요한데, 위 4에서 만든 포맷을 적용하는 시점을 알려주기 위해 만드는 JDBC 이벤트 리스너입니다. 어떤 블로그나 포럼에서는 포맷 적용 시점을 @PostConstruct 로 하던데, 그렇게 하면 테이블 create, drop 시점에 적용이 안 돼서 그렇게 하면 안 되고, 아래와 같이 Connection 이 생성되자마자 적용하면 됩니다. import com.p6spy.engine.common.ConnectionInformation; import com.p6spy.engine.event.JdbcEventListener; import com.p6spy.engine.spy.P6SpyOptions; import java.sql.SQLException; public class P6SpyEventListener extends JdbcEventListener { @Override public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) { P6SpyOptions.getActiveInstance().setLogMessageFormat(P6SpyFormatter.class.getName()); } } 이렇게 2개의 구현 클래스를 만들었으면, 아래와 같이 Bean 으로 등록해주기만 하면 설정 끝입니다. import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class P6SpyConfig { @Bean public P6SpyEventListener p6SpyCustomEventListener() { return new P6SpyEventListener(); } @Bean public P6SpyFormatter p6SpyCustomFormatter() { return new P6SpyFormatter(); } }
-
해결됨실전! Querydsl
동적으로 Order 절 만드는 코드 공유합니다
강의에서는 Query DSL 4.x 버전이어서 제네릭 없는 예제 코드인 것 같은데, 제가 올리는 버전은 5.0 버전이어서 차이가 있음을 감안해주세요.정렬 조건을 하나만 추가하는 코드입니다. 정렬 조건을 여러 개 추가하려면 반복문을 돌면서 JPAQuery 를 동적으로 확장해나가면 됩니다. // 페이징하는 메소드에서 사용 private Expression<? extends Comparable<?>> specifyMemberProperty(String prop) { prop = prop == null ? "" : prop.toLowerCase(); if (prop.equals("username")) { return member.username; } if (prop.equals("age")) { return member.age; } return member.id; } // 페이징하는 메소드 // ... .orderBy(new OrderSpecifier<>("desc".equalsIgnoreCase(dir) ? Order.DESC : Order.ASC, specifyMemberProperty(prop))) .fetch(); --저는 일단 이렇게 만들어봤는데, 다른 분께서 더 나은 코드, 더 빠른 코드가 있다면 저한테도 알려주세요.감사합니다.
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
@ManyToOne Category parent 질문 있습니다.
@ManyToOne@JoinColumn(name = "parent_id")private Category parent; @OneToMany(mappedBy = "parent")private List<Category> child = new ArrayList<>(); 안녕하세요.위 코드에서 부모 쪽에 다대일 매핑 한 이유를 알고 싶습니다.아니면 DB 테이블 안에서 부모 셀프 외래 키를 만들어서조회할 때 이 외래 키를 참조하여 자식 카테고리들까지 같이 조회하게 만들었기 때문에 테이블에서 다는 parent_id니까jpa에도 parent에 다를 준 건 가 싶기도 해서요필드 명에 부모 자식이 있어서 부모는 하나고 자식은 여러 개인데 부모 쪽에 다로 돼있어서 헷갈리네요..
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
기능이 버튼눌러서는 잘 작동안하는데 url검색하면 빠르게 동작
aws 로 서비스 배포하였는데 버튼눌러서 controller에 접근하는 url이 바뀔떄마다 엄청 느려서 기능접속이 잘안되네요 swap설정은 되어있는데 혹시 이유알수 있을까요 ??url을 직접쳐서 검색하는건 빨라요간단히 controller에서 template으로 정보 맞교환하는건데 이유 알수 있을까요? <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Hello</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" href="../css/main.css"/> </head> <body> <div class="cd-switch"> <div class="switchUp"> <h1> <p th:text="${userName}+'님의 선택은 !? '"><br></p> </h1> <p th:text="' 원하는 걸 골라봐요 왼쪽? 오른쪽? 결과는 반영됩니다! '" style=" width: 1000px;"></p> <h2 > <p th:text="${question}" style=" width: 1000px;"></p> </h2> <html> <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css"> <body> <div class="w3-light-grey"> <div class="w3-green" th:style="'height:24px;width: ' + ${LeftPercent} + '%'">LEFT</div> </div><br> <div class="w3-light-grey"> <div class="w3-red" th:style="'height:24px;width: ' + ${RightPercent} + '%'">RIGHT</div> </div><br> <div class="w3-container"> <div class="w3-bar"> <form action="http://13.124.171.10:8081/leftQuestion" method="get"> <button class="w3-button w3-left w3-light-grey" type="submit" >« LEFT!</button> </form> <form action="http://13.124.171.10:8081/rightQuestion" method="get"> <button class="w3-button w3-right w3-green">RIGHT! »</button> </form> </div> </div> </body> </html> </div> </div> </body> </html> 이런식으로 url을 직접넣어서 ec2컴퓨터에 연결하는건 안되는 건가요??
-
미해결스프링과 JPA 기반 웹 애플리케이션 개발
3:34 인증 메일 확인 - 입력값 오류
안녕하세요.아직 앞부분이지만 강의 최고입니다. 질문1테스트코드 인증 메일 확인 - 입력값 오류에 대해서 지금 로직은 이메일 자체가 없으니까 이메일이 없는 오류고 이메일은 존재하나 토큰이 일치 하지않는경우 이렇게 2가지로 테스트 코드를 진행할 필요까진 없는건가요 ? =================================질문25:45 초에는 @Transactional 을 달아주셨는데 이 이유가58 라인에 있는 newAccount.generateEmailCheckToken(); 코드를 영속객체로 관리 해주기 위함 이라고 생각하면 될까요?
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
Sequence 방식 allocation 기본 값 50에 대한 이해
안녕하세요 allocation 방식에 대해 좀 헷갈려서제가 이해한게 맞는지 문의 드릴려고 합니다.. 제 생각을 정리 해봤습니다. allocation 기본값 50 설정은 실제 DB 쿼리 문은 2번만 날려 시퀀스 값을 미리 51로 설정 해놓아 메모리상으로 51까지 순차적으로 증가시킨 후 51까지 도달되면 다시 DB로 시퀀스 증가 쿼리문을 날려 101로 증가돼 성능 이점을 챙긴다. 물리적 DBDB 한번 호출 시퀀스 값 1 / PK 값 1DB 두 번 호출 시퀀스 값 51 / PK 값 2===============================이제 메모리에서 1씩 가져옴 물리 DB PK 값이 51번까지 게속 사용PK 값이 51까지 도달된다면 DB로 시퀀스 50 증가 쿼리 전송동시성은 메모리에 이미 올라와 있는걸 사용하기 때문에 문제 없음
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
처음 저장한 데이터가 조회가 안됩니다.
안녕하세요. 현재 대학생이고 김영한님 강의를 본 후 프로젝트를 진행중입니다.다름이 아니라, 현재 Tale과 Keyword의 다대다 연관관계를 피하기 위해 중간에 TaleKeyword라는 중간 테이블을 두었고, Tale은 Member 테이블과 일대일 연관관계를 설정해두었습니다.동화(Tale)를 키워드, 회원정보에 끼워넣고 생성하는 API는 완성하였고 DB에 쌓이는 것까지 확인했습니다. 하지만, memberId를 이용해서 조회하려고 하니, 맨 처음 memberId가 1인 회원의 처음 저장한 taleId 1번의 데이터와 memberId가 2인 회원의 처음 저장한 동화인 taleId가 4인 동화가 조회가 안됩니다.(나머지 taleId 2, 3, 5번 데이터는 조회가 잘됩니다.) 쿼리문은 다음과 같이 짰고 영속성 문제인 것 같은데, 왜 첫번째 데이터만 조회가 안되는 걸까요?// 동화 목록조회(페이징) public List<Tale> findTalesByMemberId(Long memberId, int offset, int limit) { return em.createQuery( "select t from Tale t" + " join fetch t.member m" + " join fetch t.image.taleImage ti" + " where m.id = :memberId" + " order by t.createdTime desc", Tale.class ) .setParameter("memberId", memberId) .setFirstResult(offset) .setMaxResults(limit) .getResultList(); }
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
27번 라인의 order의 경우 왜 Long을 리턴값으로 작성하셨는지 궁금합니다.
단순히 void로 하셔도 무방하셨을 것 같은데, 혹시 어떤 이유가 있을까요? 혹시 이러한 패턴이 따로 있을까요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
모든 개체가 필수적으로 참여하는 일대일관계
수업에서는 다양한 예시를 위해 주문과 배송 엔티티를 일대일 관계로 나눠서 만들었지만 db공부를 하던 중 "모든 개체가 일대일 관계에 필수적으로 참여하면 릴레이션을 하나로 합친다."라는 것을 보았습니다.위 글을 바탕으로 주문엔티티는 배송엔티티를 무조건 가지고 배송엔티티도 주문엔티티를 가지니 하나의 엔티티로 합치고 주문의 기본키와 배송의 기본키 두개를 합쳐 하나의 기본키로 만든다는 생각을 했습니다.실무에서는 주문과 배송을 각각 나누어 설계하는지 아님 합쳐서 설계하는지 궁금합니다.다시말해 1대1관계를 나눠서 설계하는지 궁금합니다.
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
user domain 생성관련
안녕하세요 선생님, 7강 유저 생성 에서 질문이 생겨 남깁니다. DTO에 User를 생성하는 클래스가 있는데 domain으로 따로 빼서 User클래스를 또 생성하는 이유는 무엇인가요? 스프링부트의 구조 짜는 법을 잘 모르고 있는건가 싶기도 하고..dto는 단순히 어떤 데이터로 통신할건지 정의만 해놓고 상세한 정의는 domain 의 user에서 하는 개념인건가요? 상세한 설명 부탁드리겠씁니다..!
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
JPA와 OOP
JPA 공부하면서 느끼는 점이 엔티티 자체 또는 엔티티 간의 협력관계를 잘못 구성하면 쿼리도 점점 이상해져 간다고 생각합니다. 이에 OOP에 대한 이해가 정말 중요하다는 생각이 드는데, 맞나요?
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
말해주신대로 코드를 수정한뒤 다시 git clone 했는데요
혹시 swap 설정은 clone 할떄 마다 해줘야하나요:?? 엄청느려져서요!이런식으로 코드를 수정하였는데 엄청 속도가 느려져서 다음 url 로 접속이 안되네요 !
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
application.yml 설정하는 곳에러입니다
제 뭐가문제일까요? 제 mysql은 docker에 깔려있습니다
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
외래키 지정
연관관계의 주인을 외래키의 위치와 관련해서 정한다고 하셨는데, 이 외래키의 위치도 테이블 설계할 때, Member 테이블에 OrderId 방식으로 할지, Order 테이블에 MemberId로 할지는 자주 조회되는 형태를 기준으로 외래키 위치를 정하면 될까요?
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
@ManyToMany 대체 방법
실전에서는 @ManyToMany를 사용하지 않아서, 테이블의 N:M 관계는 중간 테이블을 이용해서 1:N, N:1로 매핑하는 것을 권장한다고 하셨는데, 중간 테이블을 이용하여 1:N, N:1로 매핑하는 걸 권장한다고 말이, @jointable 만들지 말고, 예를 들어 OrderItem 같은 엔티티와 테이블을 만드는 방법을 말씀하시는건가요?
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
배포후 사이트에 접속은 되는데 이후 과정이 안됩니다.
위험한 컨텐츠라그런지 localhost로 바뀌어버립니다.어떻게 하면 이 사이트의 서비스를 이용할 수 있을까요? 다 허용으로 바꾸었는데도 그럽니다 ㅠㅠ ++보니까 제가 url에 일일이 넣어주면 이동이 되는데 아니면 local host 로 변경이 됩니다 이유가 뭘까요> ㅠㅠ
-
미해결스프링 DB 2편 - 데이터 접근 활용 기술
테스트 DB 방안에 대해
안녕하세요~! 혹시 테스트 DB 단은 단순히 다음과 같이 명시하여 datasource: url: jdbc:h2:mem:test;MODE=MySQL # Test 는 인메모리 사용 driver-class-name: org.h2.Driver username: sa password: 메모리 DB 를 사용하는 방법은 별로 추천하지 않으실까요? 별도의 DB 를 사용하는 것이 제일 좋은 방법일까요? 메모리 DB를 사용하는것이 빠르고 편할 것 같다고 생각헀는데, 보통 어떤 방식을 실무에서 사용하는지 궁금합니다!! 감사합니다 :) (예를 들어, ci 및 build 서버에서 Test 수행시 외부 DB 서버 내 Test 전용 DB 를 통해 Test 를 한다)
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
error: cannot find symbol @Entity ^ symbol: class Entity
애플리케이션을 처음 실행하고 여러 파일에서 위 제목과 같은 오류가 납니다. 한 파일이 아니라 여러 파일에 걸쳐가며 저렇게 발생하는데 어떻게 해결해야 하나요?
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
스프링부트 버전관련 질문
안녕하세요 멘토님! 프로젝트앱을 다운받아 열었더니 빌드후에 다음과 같이 오류가 나타납니다.. 전에 프로젝트를 3.xx버전으로 했어서 이 경우에 java 17이상만 가능하기 때문에 java11을 사용하지 못해 나는 오류인것 같은데 어떻게 진행하면 될까요? problem occurred configuring root project 'library-app'. > Could not resolve all files for configuration ':classpath'. > Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.0.1. Required by: project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.0.1 > No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.0.1 was found. The consumer was configured to find a runtime of a library compatible with Java 8, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '7.5' but: - Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.1 declares a library, packaged as a jar, and its dependencies declared externally: - Incompatible because this component declares an API of a component compatible with Java 17 and the consumer needed a runtime of a component compatible with Java 8 - Other compatible attribute: - Doesn't say anything about org.gradle.plugin.api-version (required '7.5') - Variant 'javadocElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.1 declares a runtime of a component, and its dependencies declared externally: - Incompatible because this component declares documentation and the consumer needed a library - Other compatible attributes: - Doesn't say anything about its target Java version (required compatibility with Java 8) - Doesn't say anything about its elements (required them packaged as a jar) - Doesn't say anything about org.gradle.plugin.api-version (required '7.5') - Variant 'mavenOptionalApiElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.0.1 declares a library, packaged as a jar, and its dependencies declared externally: - Incompatible because this component declares an API of a component compatible with Java 17 and the consumer needed a runtime of a component compatible with Java 8 - Other compatible attribute: - Doesn't say anything about org.gradle.plugin.api-version (required '7.5') - Variant 'mavenOptionalRuntimeElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.0.1 declares a runtime of a library, packaged as a jar, and its dependencies declared externally: - Incompatible because this component declares a component compatible with Java 17 and the consumer needed a component compatible with Java 8 - Other compatible attribute: - Doesn't say anything about org.gradle.plugin.api-version (required '7.5') - Variant 'runtimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.1 declares a runtime of a library, packaged as a jar, and its dependencies declared externally: - Incompatible because this component declares a component compatible with Java 17 and the consumer needed a component compatible with Java 8 - Other compatible attribute: - Doesn't say anything about org.gradle.plugin.api-version (required '7.5') - Variant 'sourcesElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.0.1 declares a runtime of a component, and its dependencies declared externally: - Incompatible because this component declares documentation and the consumer needed a library - Other compatible attributes: - Doesn't say anything about its target Java version (required compatibility with Java 8) - Doesn't say anything about its elements (required them packaged as a jar) - Doesn't say anything about org.gradle.plugin.api-version (required '7.5')