From 9ca59938d9aee39c13e4693bdc6c56e5ee9ddf12 Mon Sep 17 00:00:00 2001 From: JiWoo Date: Fri, 12 Jan 2024 22:15:48 +0900 Subject: [PATCH] [Refactor] user refactor (#111) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(User): User의 Id에 private getter 제거 * feat(Url): Url의 pattern 변경과 상수로 미리 compile해 두도록 변경 * feat(UserQueryRepository): User 에그리거트에 하나의 repository만 만들어 사용하도록 변경 * docs(Readme): 리드미 일부 수정 * refactor(Notice): 생성자의 파라미터에 개행 추가 --- README.md | 8 +++- .../kuring/admin/common/dto/FeedbackDto.java | 11 +++--- .../notice/domain/DepartmentNotice.java | 5 ++- .../kustacks/kuring/notice/domain/Notice.java | 5 ++- .../kustacks/kuring/notice/domain/Url.java | 39 +++++++++++++++++-- .../kuring/user/business/FeedbackService.java | 18 +++------ .../user/domain/FeedbackRepository.java | 6 --- .../com/kustacks/kuring/user/domain/User.java | 1 - .../user/domain/UserQueryRepository.java | 11 ++++++ .../user/domain/UserQueryRepositoryImpl.java | 27 +++++++++++++ .../kuring/user/domain/UserRepository.java | 2 +- 11 files changed, 101 insertions(+), 32 deletions(-) delete mode 100644 src/main/java/com/kustacks/kuring/user/domain/FeedbackRepository.java create mode 100644 src/main/java/com/kustacks/kuring/user/domain/UserQueryRepository.java create mode 100644 src/main/java/com/kustacks/kuring/user/domain/UserQueryRepositoryImpl.java diff --git a/README.md b/README.md index e59f0b21..560e9874 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,12 @@ JPA 프로그래밍의 저자, 김영한 선생님의 의견을 빌리자면 다 다음 글의 3번 “양방향 연관관계 주의점”을 보면 이해할 수 있다. https://blogshine.tistory.com/345 -아예 User에서 UserCategory를 삭제하는 편이 더 좋을것 같다. 양방향 연관관계가 필수적인 포인트가 아니기 때문이다! → **User에서 제거!** +아예 User에서 UserCategory를 삭제하는 편이 더 좋을것 같다. 양방향 연관관계가 필수적인 포인트가 아니기 때문이다! → **User에서 제거!** + image + 테이블 구조에 변화는 없기에 적용가능 + --- ## 3. 공지 Scrap작업 multi-threading 처리로 시간 개선하기 @@ -419,6 +422,9 @@ Querydsl 도입으로 다음과 같은 이점을 얻었습니다. ---- ## 2. Flyway + + + dev, local 환경에서는 단순히 ddl을 create-drop 또는 update 옵션을 사용하고 있었기에 DB에 대해 고민할 필요가 없었습니다. 하지만 운영환경에서는 ddl을 validate 또는 none 옵션을 사용해야하기 때문에 초기에는 DB script를 뽑아서 별도로 관리를 했습니다. 이후 기능이 추가되면서 script가 변경되는 일이 빈번해졌고, 매번 일일이 스크립트를 관리하는 것이 번거로울 뿐 아니라 실수하기 딱 좋은 부분이라 Flyway를 도입하여 데이터베이스 형상관리를 진행했습니다. diff --git a/src/main/java/com/kustacks/kuring/admin/common/dto/FeedbackDto.java b/src/main/java/com/kustacks/kuring/admin/common/dto/FeedbackDto.java index e37a9d0c..623030f8 100644 --- a/src/main/java/com/kustacks/kuring/admin/common/dto/FeedbackDto.java +++ b/src/main/java/com/kustacks/kuring/admin/common/dto/FeedbackDto.java @@ -1,15 +1,13 @@ package com.kustacks.kuring.admin.common.dto; -import com.kustacks.kuring.user.domain.Feedback; +import com.querydsl.core.annotations.QueryProjection; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; @Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PROTECTED) public class FeedbackDto { @@ -17,7 +15,10 @@ public class FeedbackDto { private Long userId; private LocalDateTime createdAt; - public static FeedbackDto from(Feedback feedback) { - return new FeedbackDto(feedback.getContent(), feedback.getUserId(), feedback.getCreatedAt()); + @QueryProjection + public FeedbackDto(String contents, Long userId, LocalDateTime createdAt) { + this.contents = contents; + this.userId = userId; + this.createdAt = createdAt; } } diff --git a/src/main/java/com/kustacks/kuring/notice/domain/DepartmentNotice.java b/src/main/java/com/kustacks/kuring/notice/domain/DepartmentNotice.java index 4b8aa581..d4a66c43 100644 --- a/src/main/java/com/kustacks/kuring/notice/domain/DepartmentNotice.java +++ b/src/main/java/com/kustacks/kuring/notice/domain/DepartmentNotice.java @@ -19,7 +19,10 @@ public class DepartmentNotice extends Notice { private DepartmentName departmentName; @Builder - public DepartmentNotice(String articleId, String postedDate, String updatedDate, String subject, CategoryName categoryName, Boolean important, String fullUrl, DepartmentName departmentName) { + public DepartmentNotice(String articleId, String postedDate, String updatedDate, + String subject, CategoryName categoryName, Boolean important, + String fullUrl, DepartmentName departmentName) + { super(articleId, postedDate, updatedDate, subject, categoryName, important, fullUrl); this.departmentName = departmentName; } diff --git a/src/main/java/com/kustacks/kuring/notice/domain/Notice.java b/src/main/java/com/kustacks/kuring/notice/domain/Notice.java index 7816d167..13786e49 100644 --- a/src/main/java/com/kustacks/kuring/notice/domain/Notice.java +++ b/src/main/java/com/kustacks/kuring/notice/domain/Notice.java @@ -42,7 +42,10 @@ public class Notice { @Column(name = "category_name", nullable = false) private CategoryName categoryName; - public Notice(String articleId, String postedDate, String updatedDate, String subject, CategoryName categoryName, Boolean important, String fullUrl) { + public Notice(String articleId, String postedDate, String updatedDate, + String subject, CategoryName categoryName, Boolean important, + String fullUrl) + { this.articleId = articleId; this.postedDate = postedDate; this.updatedDate = updatedDate; diff --git a/src/main/java/com/kustacks/kuring/notice/domain/Url.java b/src/main/java/com/kustacks/kuring/notice/domain/Url.java index db834781..d58d0ff0 100644 --- a/src/main/java/com/kustacks/kuring/notice/domain/Url.java +++ b/src/main/java/com/kustacks/kuring/notice/domain/Url.java @@ -16,7 +16,38 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Url { - private static final String REGEX_PATTERN = "^((http|https)://)?([a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?)$"; + /** + * Regular Expression by RFC 3986 for URI Validation + * @link https://datatracker.ietf.org/doc/html/rfc3986#appendix-B + * @author jiwoo + */ + private static final String REGEX_SCHEME = "[A-Za-z][+-.\\w^_]*:"; + + // Example: "//". + private static final String REGEX_AUTHORATIVE_DECLARATION = "/{2}"; + + // Optional component. Example: "suzie:abc123@". The use of the format "user:password" is deprecated. + private static final String REGEX_USERINFO = "(?:\\S+(?::\\S*)?@)?"; + + // Examples: "fitbit.com", "22.231.113.64". + private static final String REGEX_HOST = "(?:" + // @Author = http://www.regular-expressions.info/examples.html + // IP address + "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" + + "|" + // host name + "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" + // domain name + "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" + // TLD identifier must have >= 2 characters + "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))"; + + // Example: ":8042". + private static final String REGEX_PORT = "(?::\\d{2,5})?"; + + //Example: "/user/heartrate?foo=bar#element1". + private static final String REGEX_RESOURCE_PATH = "(?:/\\S*)?"; + + private static final String REGEX_URL = "^(?:(?:" + REGEX_SCHEME + REGEX_AUTHORATIVE_DECLARATION + ")?" + + REGEX_USERINFO + REGEX_HOST + REGEX_PORT + REGEX_RESOURCE_PATH + ")$"; + + private static final Pattern compiledUrlPattern = Pattern.compile(REGEX_URL); @Column(name = "url", length = 256, nullable = false) private String value; @@ -30,11 +61,11 @@ public Url(String fullUrl) { } private boolean isValidUrl(String fullUrl) { - return !Objects.isNull(fullUrl) && patternMatches(fullUrl, REGEX_PATTERN); + return !Objects.isNull(fullUrl) && patternMatches(fullUrl); } - private boolean patternMatches(String targetUrl, String regexPattern) { - return Pattern.compile(regexPattern) + private boolean patternMatches(String targetUrl) { + return compiledUrlPattern .matcher(targetUrl) .matches(); } diff --git a/src/main/java/com/kustacks/kuring/user/business/FeedbackService.java b/src/main/java/com/kustacks/kuring/user/business/FeedbackService.java index d8a9eb98..c59db0ba 100644 --- a/src/main/java/com/kustacks/kuring/user/business/FeedbackService.java +++ b/src/main/java/com/kustacks/kuring/user/business/FeedbackService.java @@ -1,22 +1,19 @@ package com.kustacks.kuring.user.business; import com.kustacks.kuring.admin.common.dto.FeedbackDto; -import com.kustacks.kuring.common.exception.code.ErrorCode; import com.kustacks.kuring.common.exception.NotFoundException; -import com.kustacks.kuring.user.domain.FeedbackRepository; +import com.kustacks.kuring.common.exception.code.ErrorCode; import com.kustacks.kuring.message.firebase.FirebaseService; import com.kustacks.kuring.message.firebase.ServerProperties; import com.kustacks.kuring.user.domain.User; import com.kustacks.kuring.user.domain.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static com.kustacks.kuring.message.firebase.FirebaseService.ALL_DEVICE_SUBSCRIBED_TOPIC; @@ -26,7 +23,6 @@ public class FeedbackService { private final UserRepository userRepository; - private final FeedbackRepository feedbackRepository; private final FirebaseService firebaseService; private final ServerProperties serverProperties; @@ -37,17 +33,15 @@ public void saveFeedback(String token, String content) { firebaseService.subscribe(token, serverProperties.ifDevThenAddSuffix(ALL_DEVICE_SUBSCRIBED_TOPIC)); } - User findUser = optionalUser.orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); + User findUser = optionalUser + .orElseThrow(() -> new NotFoundException(ErrorCode.USER_NOT_FOUND)); findUser.addFeedback(content); } + @Transactional(readOnly = true) public List lookupFeedbacks(int page, int size) { - PageRequest pageRequest = PageRequest.of(page, size, Sort.Direction.DESC, "createdAt"); - - return feedbackRepository.findAll(pageRequest) - .stream() - .map(FeedbackDto::from) - .collect(Collectors.toList()); + PageRequest pageRequest = PageRequest.of(page, size); + return userRepository.findAllFeedbackByPageRequest(pageRequest); } } diff --git a/src/main/java/com/kustacks/kuring/user/domain/FeedbackRepository.java b/src/main/java/com/kustacks/kuring/user/domain/FeedbackRepository.java deleted file mode 100644 index c7f9a4cc..00000000 --- a/src/main/java/com/kustacks/kuring/user/domain/FeedbackRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.kustacks.kuring.user.domain; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface FeedbackRepository extends JpaRepository { -} diff --git a/src/main/java/com/kustacks/kuring/user/domain/User.java b/src/main/java/com/kustacks/kuring/user/domain/User.java index 788e39fe..25af1bfa 100644 --- a/src/main/java/com/kustacks/kuring/user/domain/User.java +++ b/src/main/java/com/kustacks/kuring/user/domain/User.java @@ -24,7 +24,6 @@ public class User implements Serializable { @Id - @Getter(AccessLevel.PRIVATE) @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; diff --git a/src/main/java/com/kustacks/kuring/user/domain/UserQueryRepository.java b/src/main/java/com/kustacks/kuring/user/domain/UserQueryRepository.java new file mode 100644 index 00000000..5a493589 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/user/domain/UserQueryRepository.java @@ -0,0 +1,11 @@ +package com.kustacks.kuring.user.domain; + +import com.kustacks.kuring.admin.common.dto.FeedbackDto; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface UserQueryRepository { + + List findAllFeedbackByPageRequest(Pageable pageable); +} diff --git a/src/main/java/com/kustacks/kuring/user/domain/UserQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/user/domain/UserQueryRepositoryImpl.java new file mode 100644 index 00000000..2a69c011 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/user/domain/UserQueryRepositoryImpl.java @@ -0,0 +1,27 @@ +package com.kustacks.kuring.user.domain; + +import com.kustacks.kuring.admin.common.dto.FeedbackDto; +import com.kustacks.kuring.admin.common.dto.QFeedbackDto; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +import static com.kustacks.kuring.user.domain.QFeedback.feedback; + +@RequiredArgsConstructor +public class UserQueryRepositoryImpl implements UserQueryRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findAllFeedbackByPageRequest(Pageable pageable) { + return queryFactory.select(new QFeedbackDto(feedback.content.value, feedback.user.id, feedback.createdAt)) + .from(feedback) + .orderBy(feedback.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } +} diff --git a/src/main/java/com/kustacks/kuring/user/domain/UserRepository.java b/src/main/java/com/kustacks/kuring/user/domain/UserRepository.java index a7ac5762..f3c04123 100644 --- a/src/main/java/com/kustacks/kuring/user/domain/UserRepository.java +++ b/src/main/java/com/kustacks/kuring/user/domain/UserRepository.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Optional; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository, UserQueryRepository { Optional findByToken(String token); @Query("SELECT u.token FROM User u")