-
Notifications
You must be signed in to change notification settings - Fork 2
[#142][FEATURE] 그룹 해시태그 기능 추가 #144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
0b6aa3c
Feat: 해시태그 엔티티 추가
unikal1 0d358e3
Feat: 해시태그 dao 추가
unikal1 0668238
Feat: displayTag 추가
unikal1 74b966d
Feat: 그룹 생성 시 hash tag 추가 로직 추가(service~)
unikal1 69891f1
Feat: groupHashTag 에 group-hashtag unique 제약 조건 추가
unikal1 be4864d
Fix : copilot 리뷰 적용
unikal1 abdd478
Feat: 재시도용 retry 어노테이션 작성
unikal1 72e7c91
Test: retryTx aspect 에 대한 테스트
unikal1 7dfbc98
Refactor: RetryTx 적용 후, 자체적인 재시도 전략 제거
unikal1 cd3882e
Fix : clearAutomatically 제거
unikal1 f0d8269
Fix : findByPrefix -> 일반적인 검색 명령어 search 로 변경
unikal1 9bb405d
Build: application.properties 정리 및 테스트 환경 개편
unikal1 8a6f6cc
Test: 테스트 반영
unikal1 33ef14f
Build: 테스트 시 활성화되는 profile override 설정
unikal1 b76984d
Build: 환경변수 인자 수정
unikal1 6a9dc00
Fix: PR 리뷰 적용
unikal1 a30f15f
Fix: 기본 해시태그 null 입력을 빈 리스트로 처리
unikal1 7c9db3d
Merge branch 'dev' into feat/group-hashtag
unikal1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
36 changes: 36 additions & 0 deletions
36
src/main/java/com/studypals/domain/groupManage/dao/GroupHashTagRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.studypals.domain.groupManage.dao; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import com.studypals.domain.groupManage.entity.GroupHashTag; | ||
|
|
||
| /** | ||
| * {@link GroupHashTag} 에 대한 dao 클래스입니다. | ||
| * | ||
| * @author jack8 | ||
| * @see GroupHashTag | ||
| * @since 2025-12-23 | ||
| */ | ||
| @Repository | ||
| public interface GroupHashTagRepository extends JpaRepository<GroupHashTag, Long> { | ||
|
|
||
| /** | ||
| * 일반적인 {@code findAllByGroupId} 와 결과가 동일하나, hashTag 에 대한 fetch join 을 통한 | ||
| * N+1 문제를 방지하였습니다. | ||
| * @param groupId 검색하고자 하는 그룹의 아이디 | ||
| * @return hash tag 가 fetch join 된 groupHashTag 리스트 | ||
| */ | ||
| @Query( | ||
| """ | ||
| SELECT gt | ||
| FROM GroupHashTag gt | ||
| JOIN FETCH gt.hashTag | ||
| WHERE gt.group.id = :groupId | ||
| """) | ||
| List<GroupHashTag> findAllByGroupIdWithTag(@Param("groupId") Long groupId); | ||
| } |
106 changes: 106 additions & 0 deletions
106
src/main/java/com/studypals/domain/groupManage/dao/HashTagRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| package com.studypals.domain.groupManage.dao; | ||
|
|
||
| import java.util.Collection; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.data.jpa.repository.Modifying; | ||
| import org.springframework.data.jpa.repository.Query; | ||
| import org.springframework.data.repository.query.Param; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import com.studypals.domain.groupManage.entity.HashTag; | ||
|
|
||
| /** | ||
| * {@link HashTag} 에 대한 dao 클래스입니다. | ||
| * | ||
| * @author jack8 | ||
| * @see HashTag | ||
| * @since 2025-12-23 | ||
| */ | ||
| @Repository | ||
| public interface HashTagRepository extends JpaRepository<HashTag, Long> { | ||
|
|
||
| /** | ||
| * tag 에 대해 객체를 반환합니다. | ||
| * @param tag 검색할 태그(정확히 일치) | ||
| * @return Optional Hash tag | ||
| */ | ||
| Optional<HashTag> findByTag(String tag); | ||
|
|
||
| /** | ||
| * tag 자동완성 시 사용할 메서드. 특정 검색어에 대해 cnt 로 정렬한 데이터를 반환합니다.<br> | ||
| * | ||
| * @param value 검색할 인자(접두사 / 순서대로) | ||
sleepyhoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @param pageable 반환 개수 지정 | ||
| * @return cnt 개수 만큼의, 사용 빈도가 높은 데이터 | ||
| */ | ||
| @Query( | ||
| """ | ||
| SELECT t.tag | ||
| FROM HashTag t | ||
| WHERE t.tag LIKE CONCAT('%', :value, '%') | ||
| ORDER BY t.usedCount DESC | ||
| """) | ||
| List<String> search(@Param("value") String value, Pageable pageable); | ||
|
|
||
| /** | ||
| * usedCount 값을 원자적으로 증가시키는 메서드입니다. 해당 메서드가 실행 되면 | ||
| * 이미 해당 태그를 사용했다는 의미이므로 deletedAt 을 초기화합니다. | ||
| * @param tag 증가시킬 태그 | ||
| * @return 변경된 row 수 | ||
| */ | ||
| @Modifying | ||
| @Query( | ||
| """ | ||
| UPDATE HashTag t | ||
| SET t.usedCount = t.usedCount + 1, | ||
| t.deletedAt = null | ||
| WHERE t.tag = :tag | ||
| """) | ||
| Integer increaseUsedCount(String tag); | ||
|
|
||
| /** | ||
| * usedCount 값을 원자적으로 증가시키는 메서드입니다. 단, 여러 tags 들에 대해 연산을 수행합니다. | ||
| * @param tags 증가시킬 태그들 | ||
| * @return 변경된 row 수 | ||
| */ | ||
| @Modifying | ||
| @Query( | ||
| """ | ||
| UPDATE HashTag t | ||
| SET t.usedCount = t.usedCount + 1, | ||
| t.deletedAt = null | ||
| WHERE t.tag in (:tags) | ||
| """) | ||
| void increaseUsedCountBulk(@Param("tags") Collection<String> tags); | ||
|
|
||
| /** | ||
| * usedCount 값을 원자적으로 감소시키는 메서드입니다. 만약 0이 되면, | ||
| * 그때부터 deletedAt 을 현재 시간으로 설정합니다. n 일 이후 자동 삭제됩니다(최적화, 배치 서버 분리) | ||
| * @param tag 감소시킬 태그 | ||
| * @return 변경된 row 수 | ||
| */ | ||
| @Modifying | ||
| @Query( | ||
| """ | ||
| UPDATE HashTag t | ||
| SET t.usedCount = t.usedCount - 1, | ||
| t.deletedAt = CASE | ||
| WHEN (t.usedCount - 1) = 0 THEN CURRENT_TIMESTAMP | ||
| ELSE t.deletedAt | ||
| END | ||
| WHERE t.tag = :tag | ||
| AND t.usedCount > 0 | ||
| """) | ||
| Long decreaseUsedCount(String tag); | ||
|
|
||
| /** | ||
| * tag 리스트에 대한 전체 조회 반환 메서드입니다. | ||
| * @param tags 문자열 리스트 | ||
| * @return 파라미터에 대해 정확히 일치하는 hash tag 엔티티 리스트 | ||
| */ | ||
| List<HashTag> findAllByTagIn(Collection<String> tags); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/main/java/com/studypals/domain/groupManage/entity/GroupHashTag.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.studypals.domain.groupManage.entity; | ||
|
|
||
| import jakarta.persistence.*; | ||
|
|
||
| import lombok.*; | ||
|
|
||
| /** | ||
| * group 과 hashTag 간의 매핑 테이블입니다. hashTag 조회 시 N + 1 문제를 조심해야 할 필요가 있습니다. | ||
| * | ||
| * @author jack8 | ||
| * @see HashTag | ||
| * @see Group | ||
| * @since 2025-12-23 | ||
| */ | ||
| @Entity | ||
| @Builder | ||
| @Getter | ||
unikal1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @AllArgsConstructor | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Table( | ||
| name = "group_hash_tag", | ||
| uniqueConstraints = {@UniqueConstraint(columnNames = {"group_id", "hash_tag_id"})}) | ||
| public class GroupHashTag { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "id", nullable = false, unique = true) | ||
| private Long id; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "group_id") | ||
| private Group group; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "hash_tag_id") | ||
| private HashTag hashTag; | ||
|
|
||
| @Column(name = "display_tag", nullable = false) | ||
| private String displayTag; | ||
| } | ||
40 changes: 40 additions & 0 deletions
40
src/main/java/com/studypals/domain/groupManage/entity/HashTag.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.studypals.domain.groupManage.entity; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| import jakarta.persistence.*; | ||
|
|
||
| import lombok.*; | ||
|
|
||
| /** | ||
| * group 에 대한 hashtag 엔티티입니다. group 과 N:M 관계이며 중간 매핑 테이블이 존재합니다. | ||
| * | ||
| * @author jack8 | ||
| * @see Group | ||
| * @since 2025-12-23 | ||
| */ | ||
| @Entity | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Builder | ||
| @Getter | ||
| @Table( | ||
| name = "hash_tag", | ||
| uniqueConstraints = {@UniqueConstraint(columnNames = "tag")}) | ||
| public class HashTag { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "id", nullable = false, unique = true) | ||
| private Long id; | ||
|
|
||
| @Column(name = "tag", nullable = false, unique = true) | ||
| private String tag; | ||
|
|
||
| @Builder.Default | ||
| @Column(name = "used_count", nullable = false) | ||
| private Long usedCount = 1L; | ||
sleepyhoon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Column(name = "deleted_at", nullable = true) | ||
| private LocalDate deletedAt; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.