Skip to content

Commit

Permalink
Merge pull request #456 from tukcomCD2024/performance/group_detail-B-…
Browse files Browse the repository at this point in the history
…#455

performance : 그룹 상세조회 쿼리 개선
  • Loading branch information
seokho-1116 authored May 29, 2024
2 parents 550cd0f + a8d9bf2 commit 3ef8116
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public record GroupDetailDto(
String groupDescription,
String groupProfileUrl,
ZonedDateTime createdAt,
Boolean isOwner,
List<GroupMemberDto> members
) {

Expand All @@ -16,9 +17,10 @@ public static GroupDetailDto as(
String groupDescription,
String groupProfileUrl,
ZonedDateTime createdAt,
Boolean isOwner,
List<GroupMemberDto> members
) {
return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, members);
return new GroupDetailDto(groupName, groupDescription, groupProfileUrl, createdAt, isOwner, members);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ public record GroupDetailTotalDto(
public static GroupDetailTotalDto as(
final GroupDetailDto groupDetailDto,
final Long groupCapsuleTotalCount,
final Boolean canGroupEdit,
final List<Long> friendIds
) {
final List<GroupMemberWithRelationDto> membersWithRelation = groupDetailDto.members()
Expand All @@ -33,7 +32,7 @@ public static GroupDetailTotalDto as(
groupDetailDto.groupProfileUrl(),
groupDetailDto.createdAt(),
groupCapsuleTotalCount,
canGroupEdit,
groupDetailDto.isOwner(),
membersWithRelation
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,4 @@ Slice<GroupSummaryDto> findGroupsSlice(

Optional<GroupDetailDto> findGroupDetailByGroupIdAndMemberId(final Long groupId,
final Long memberId);

Boolean findGroupEditPermission(final Long groupId, final Long memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import static site.timecapsulearchive.core.domain.member.entity.QMember.member;
import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -58,13 +61,20 @@ public Slice<GroupSummaryDto> findGroupsSlice(
return new SliceImpl<>(groups, Pageable.ofSize(size), groups.size() > size);
}

/**
* 사용자를 제외한 그룹원 정보와 그룹의 상세정보를 반환한다.
* @param groupId 상세정보를 찾을 그룹 아이디
* @param memberId 사용자 아이디
* @return 그룹의 상세정보({@code memberId} 제외 그룹원)
*/
public Optional<GroupDetailDto> findGroupDetailByGroupIdAndMemberId(final Long groupId,
final Long memberId) {
GroupDetailDto groupDetailDtoIncludeMe =
jpaQueryFactory
.selectFrom(group)
.join(memberGroup).on(memberGroup.group.id.eq(groupId))
.join(memberGroup).on(memberGroup.group.id.eq(group.id))
.join(member).on(member.id.eq(memberGroup.member.id))
.where(group.id.eq(groupId))
.transform(
groupBy(group.id).as(
Projections.constructor(
Expand All @@ -73,6 +83,7 @@ public Optional<GroupDetailDto> findGroupDetailByGroupIdAndMemberId(final Long g
group.groupDescription,
group.groupProfileUrl,
group.createdAt,
Expressions.asBoolean(Boolean.FALSE),
list(
Projections.constructor(
GroupMemberDto.class,
Expand All @@ -92,24 +103,25 @@ public Optional<GroupDetailDto> findGroupDetailByGroupIdAndMemberId(final Long g
return Optional.empty();
}

List<GroupMemberDto> groupMemberDtosExcludeMe = groupDetailDtoIncludeMe.members().stream()
.filter(memberDto -> !memberDto.memberId().equals(memberId))
.toList();
boolean isOwner = false;
List<GroupMemberDto> groupMemberDtosExcludeMe = new ArrayList<>();
for (GroupMemberDto dto : groupDetailDtoIncludeMe.members()) {
if (!dto.memberId().equals(memberId)) {
groupMemberDtosExcludeMe.add(dto);
} else {
isOwner = dto.isOwner();
}
}

return Optional.of(
GroupDetailDto.as(
groupDetailDtoIncludeMe.groupName(),
groupDetailDtoIncludeMe.groupDescription(),
groupDetailDtoIncludeMe.groupProfileUrl(),
groupDetailDtoIncludeMe.createdAt(),
groupMemberDtosExcludeMe)
isOwner,
groupMemberDtosExcludeMe
)
);
}

public Boolean findGroupEditPermission(final Long groupId, final Long memberId) {
return jpaQueryFactory.select(memberGroup.isOwner)
.from(memberGroup)
.where(memberGroup.group.id.eq(groupId).and(memberGroup.member.id.eq(memberId)))
.fetchOne();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ public GroupDetailTotalDto findGroupDetailByGroupId(final Long memberId, final L
groupId, memberId).orElseThrow(GroupNotFoundException::new);

final Long groupCapsuleCount = groupCapsuleQueryRepository.findGroupCapsuleCount(groupId);
final Boolean canGroupEdit = groupRepository.findGroupEditPermission(groupId, memberId);

final List<Long> groupMemberIds = groupDetailDto.members().stream()
.map(GroupMemberDto::memberId)
.toList();
final List<Long> friendIds = memberFriendRepository.findFriendIds(groupMemberIds, memberId);

return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, canGroupEdit, friendIds);
return GroupDetailTotalDto.as(groupDetailDto, groupCapsuleCount, friendIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package site.timecapsulearchive.core.domain.group.repository;

import static org.assertj.core.api.Assertions.assertThat;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
import org.assertj.core.api.SoftAssertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestConstructor;
import org.springframework.test.context.TestConstructor.AutowireMode;
import org.springframework.transaction.annotation.Transactional;
import site.timecapsulearchive.core.common.RepositoryTest;
import site.timecapsulearchive.core.common.fixture.domain.GroupFixture;
import site.timecapsulearchive.core.common.fixture.domain.MemberFixture;
import site.timecapsulearchive.core.common.fixture.domain.MemberGroupFixture;
import site.timecapsulearchive.core.domain.group.data.dto.GroupDetailDto;
import site.timecapsulearchive.core.domain.group.data.dto.GroupMemberDto;
import site.timecapsulearchive.core.domain.group.entity.Group;
import site.timecapsulearchive.core.domain.member.entity.Member;
import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup;

@TestConstructor(autowireMode = AutowireMode.ALL)
class GroupQueryRepositoryImplTest extends RepositoryTest {

private final GroupQueryRepository groupQueryRepository;

private Long groupId;
private Long ownerId;
private Long groupMemberId;

GroupQueryRepositoryImplTest(JPAQueryFactory jpaQueryFactory) {
this.groupQueryRepository = new GroupQueryRepositoryImpl(jpaQueryFactory);
}

@Transactional
@BeforeEach
void setup(@Autowired EntityManager entityManager) {
Group group = GroupFixture.group();
entityManager.persist(group);
groupId = group.getId();

Member member = MemberFixture.member(1);
entityManager.persist(member);
ownerId = member.getId();

MemberGroup memberGroup = MemberGroupFixture.groupOwner(member, group);
entityManager.persist(memberGroup);

List<Member> members = MemberFixture.members(2, 10);
for (Member m : members) {
entityManager.persist(m);

MemberGroup mg = MemberGroupFixture.memberGroup(m, group, Boolean.FALSE);
entityManager.persist(mg);
}
groupMemberId = members.get(0).getId();
}

@Test
void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_상세가_반환된다() {
//given
//when
GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, ownerId).orElseThrow();

//then
assertThat(groupDetail).isNotNull();
}

@Test
void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_그룹_정보를_볼_수_있다() {
//given
//when
GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, ownerId).orElseThrow();

//then
SoftAssertions.assertSoftly(softly -> {
assertThat(groupDetail.groupName()).isNotBlank();
assertThat(groupDetail.groupDescription()).isNotBlank();
assertThat(groupDetail.groupProfileUrl()).isNotBlank();
assertThat(groupDetail.createdAt()).isNotNull();
assertThat(groupDetail.members()).isNotEmpty();
});
}

@Test
void 그룹_아이디와_멤버_아이디_그룹을_조회하면_그룹원들의_정보를_볼_수_있다() {
//given
//when
GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, groupMemberId).orElseThrow();

//then
SoftAssertions.assertSoftly(softly -> {
assertThat(groupDetail.members()).isNotEmpty();
assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.memberId()).isNotNull());
assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.tag()).isNotBlank());
assertThat(groupDetail.members()).allSatisfy(
m -> assertThat(m.nickname()).isNotBlank());
assertThat(groupDetail.members()).allSatisfy(
m -> assertThat(m.profileUrl()).isNotBlank());
assertThat(groupDetail.members()).allSatisfy(m -> assertThat(m.isOwner()).isNotNull());
});
}

@Test
void 그룹_아이디와_멤버_아이디로_그룹을_조회하면_본인은_포함되어_조회되지_않는다() {
//given
//when
GroupDetailDto groupDetail = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, groupMemberId).orElseThrow();

List<GroupMemberDto> groupMemberDtos = groupDetail.members();

//then
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(groupMemberDtos)
.noneMatch(member -> member.memberId().equals(groupMemberId));
});
}

@Test
void 그룹_아이디로_그룹을_상세정보를_조회하면_사용자를_제외한_그룹원의_정보를_볼_수_있다() {
//given
//when
GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, ownerId)
.orElseThrow();

//then
assertThat(groupDetailDto.members()).noneMatch(m -> m.memberId().equals(ownerId));
}

@Test
void 존재하지_않는_그룹_아이디로_그룹을_상세정보를_조회하면_예외가_발생한다() {
//given
Long notExistGroupId = 999L;

//when
Optional<GroupDetailDto> groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
notExistGroupId, ownerId);

//then
assertThat(groupDetailDto).isEmpty();
}

@Test
void 그룹장이_그룹을_상세정보를_조회하면_그룹의_수정권한을_가진다() {
//given
//when
GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, ownerId)
.orElseThrow();

//then
assertThat(groupDetailDto.isOwner()).isTrue();
}

@Test
void 그룹원이_그룹을_상세정보를_조회하면_그룹의_수정권한이_없다() {
//given
//when
GroupDetailDto groupDetailDto = groupQueryRepository.findGroupDetailByGroupIdAndMemberId(
groupId, groupMemberId)
.orElseThrow();

//then
assertThat(groupDetailDto.isOwner()).isFalse();
}
}
Loading

0 comments on commit 3ef8116

Please sign in to comment.