Skip to content
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

performance : 그룹 상세조회 쿼리 개선 #456

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,193 @@
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 {

seokho-1116 marked this conversation as resolved.
Show resolved Hide resolved
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
seokho-1116 marked this conversation as resolved.
Show resolved Hide resolved
//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();
});
}

@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
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(groupDetailDto.groupName()).isNotBlank();
softly.assertThat(groupDetailDto.groupDescription()).isNotBlank();
softly.assertThat(groupDetailDto.groupProfileUrl()).isNotBlank();
softly.assertThat(groupDetailDto.groupProfileUrl()).isNotBlank();
softly.assertThat(groupDetailDto.createdAt()).isNotNull();
softly.assertThat(groupDetailDto.members()).isNotEmpty();
});
}

@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
Loading