From 74682b46c1805f3e524ddc99054a8dd7f2c1b3fe Mon Sep 17 00:00:00 2001 From: joowojr Date: Tue, 18 Feb 2025 11:57:09 +0900 Subject: [PATCH 1/6] =?UTF-8?q?CLAP-451=20Refactor:=20=EC=A7=80=EC=97=B0?= =?UTF-8?q?=20=EB=A1=9C=EB=94=A9=EC=9D=B4=20=EC=84=A4=EC=A0=95=EB=90=98?= =?UTF-8?q?=EC=96=B4=20=EC=9E=88=EB=8A=94=20admin=20=EA=B3=BC=20department?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=B4=20=EC=B6=94=EA=B0=80=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=BF=BC=EB=A6=AC=EA=B0=80=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EB=A7=A4?= =?UTF-8?q?=ED=8D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #590 --- .../mapper/DepartmentPersistenceMapper.java | 4 +- .../mapper/MemberPersistenceMapper.java | 57 +++++++++++++++---- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/main/java/clap/server/adapter/outbound/persistense/mapper/DepartmentPersistenceMapper.java b/src/main/java/clap/server/adapter/outbound/persistense/mapper/DepartmentPersistenceMapper.java index 436c3f64..41b32113 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/mapper/DepartmentPersistenceMapper.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/mapper/DepartmentPersistenceMapper.java @@ -6,10 +6,10 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; -@Mapper(componentModel = "spring", uses = {MemberPersistenceMapper.class}) +@Mapper(componentModel = "spring") public interface DepartmentPersistenceMapper extends PersistenceMapper { - @Mapping(source = "admin.memberId", target = "adminId") + @Mapping(target = "adminId", ignore = true) @Mapping(source = "manager", target = "isManager") Department toDomain(DepartmentEntity entity); diff --git a/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java b/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java index f0775c2a..3d877d51 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java @@ -1,32 +1,64 @@ package clap.server.adapter.outbound.persistense.mapper; +import clap.server.adapter.outbound.persistense.entity.member.DepartmentEntity; import clap.server.adapter.outbound.persistense.entity.member.MemberEntity; +import clap.server.domain.model.member.Department; import clap.server.domain.model.member.Member; import clap.server.domain.model.member.MemberInfo; +import org.hibernate.Hibernate; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; import org.springframework.beans.factory.annotation.Autowired; -@Mapper(componentModel = "spring", uses = {DepartmentPersistenceMapper.class}) +@Mapper(componentModel = "spring") public abstract class MemberPersistenceMapper { @Autowired protected DepartmentPersistenceMapper departmentPersistenceMapper; - @Mapping(source = "name", target = "memberInfo.name") - @Mapping(source = "email", target = "memberInfo.email") - @Mapping(source = "nickname", target = "memberInfo.nickname") - @Mapping(source = "role", target = "memberInfo.role") - @Mapping(source = "departmentRole", target = "memberInfo.departmentRole") - @Mapping(source = "department", target = "memberInfo.department") - @Mapping(source = "reviewer", target = "memberInfo.isReviewer") - @Mapping(source = "admin", target = "admin", qualifiedByName = "toDomain") - @Mapping(source = "createdAt", target = "createdAt") - @Mapping(source = "updatedAt", target = "updatedAt") - @Mapping(source = "memberId", target = "memberId") + @Mapping(target = "memberInfo", expression = "java(memberEntityToMemberInfo(entity))") + @Mapping(target = "department", expression = "java(mapDepartment(entity))") + @Mapping(target = "admin", ignore = true) + @Mapping(source = "entity.createdAt", target = "createdAt") + @Mapping(source = "entity.updatedAt", target = "updatedAt") + @Mapping(source = "entity.memberId", target = "memberId") public abstract Member toDomain(MemberEntity entity); + protected MemberInfo memberEntityToMemberInfo(MemberEntity memberEntity) { + if (memberEntity == null) { + return null; + } + + DepartmentEntity departmentEntity = memberEntity.getDepartment(); + Department department = (departmentEntity != null && Hibernate.isInitialized(departmentEntity)) + ? departmentPersistenceMapper.toDomain(departmentEntity) + : null; + + return MemberInfo.builder() + .name(memberEntity.getName()) + .email(memberEntity.getEmail()) + .nickname(memberEntity.getNickname()) + .role(memberEntity.getRole()) + .departmentRole(memberEntity.getDepartmentRole()) + .isReviewer(memberEntity.isReviewer()) + .department(department) + .build(); + } + + protected Department mapDepartment(MemberEntity entity) { + DepartmentEntity department = entity.getDepartment(); + if (department == null) { + return null; + } + + if (!Hibernate.isInitialized(department)) { + return null; + } + + return departmentPersistenceMapper.toDomain(department); + } + @Mapping(source = "memberInfo.name", target = "name") @Mapping(source = "memberInfo.email", target = "email") @Mapping(source = "memberInfo.nickname", target = "nickname") @@ -40,6 +72,7 @@ public abstract class MemberPersistenceMapper { @Mapping(source = "memberId", target = "memberId") public abstract MemberEntity toEntity(Member member); + @Named("toDomain") protected Member toDomainAdmin(MemberEntity admin) { if (admin == null) return null; From 7f3b821c1915bd96b22a2eaf3962c886341a675b Mon Sep 17 00:00:00 2001 From: joowojr Date: Tue, 18 Feb 2025 12:01:14 +0900 Subject: [PATCH 2/6] =?UTF-8?q?CLAP-451=20Refactor:=20department=20fetch?= =?UTF-8?q?=EA=B0=80=20=ED=95=84=EC=9A=94=ED=95=9C=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20=EB=8C=80=ED=95=B4=EC=84=9C=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #590 --- .../persistense/MemberPersistenceAdapter.java | 14 +++++++++++++- .../member/MemberCustomRepositoryImpl.java | 1 + .../repository/member/MemberRepository.java | 14 ++++++++++++-- .../port/outbound/member/LoadMemberPort.java | 4 ++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java index ca4f88f5..1ee549c7 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/MemberPersistenceAdapter.java @@ -31,12 +31,24 @@ public Optional findById(final Long id) { return memberEntity.map(memberPersistenceMapper::toDomain); } + @Override + public Optional findByIdWithFetchDepartment(Long id) { + Optional memberEntity = memberRepository.findByIdWithFetchDepartment(id); + return memberEntity.map(memberPersistenceMapper::toDomain); + } + @Override public Optional findActiveMemberById(final Long id) { Optional memberEntity = memberRepository.findByStatusAndMemberId(MemberStatus.ACTIVE, id); return memberEntity.map(memberPersistenceMapper::toDomain); } + @Override + public Optional findActiveMemberByIdWithFetchDepartment(Long id) { + Optional memberEntity = memberRepository.findActiveMemberByIdWithFetchDepartment(id); + return memberEntity.map(memberPersistenceMapper::toDomain); + } + @Override public Optional findActiveMemberByNickname(final String nickname) { Optional memberEntity = memberRepository.findActiveMemberByNickname(nickname); @@ -85,7 +97,7 @@ public List findActiveManagers() { @Override public Page findAllMembers(final Pageable pageable) { - return memberRepository.findAllMembers(pageable).map(memberPersistenceMapper::toDomain); + return memberRepository.findAllMembersWithFetchDepartment(pageable).map(memberPersistenceMapper::toDomain); } @Override diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberCustomRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberCustomRepositoryImpl.java index 966cb823..a02a618a 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberCustomRepositoryImpl.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberCustomRepositoryImpl.java @@ -25,6 +25,7 @@ public class MemberCustomRepositoryImpl implements MemberCustomRepository { private Page executeQueryWithPageable(Pageable pageable, BooleanBuilder whereClause, OrderSpecifier orderSpecifier) { List result = queryFactory .selectFrom(memberEntity) + .leftJoin(memberEntity.department).fetchJoin() .where(whereClause) .orderBy(orderSpecifier) .offset(pageable.getOffset()) diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java index 3f563567..391ee98d 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/member/MemberRepository.java @@ -3,6 +3,8 @@ import clap.server.adapter.outbound.persistense.entity.member.MemberEntity; import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; import clap.server.adapter.outbound.persistense.entity.member.constant.MemberStatus; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,7 +15,13 @@ import java.util.Set; @Repository -public interface MemberRepository extends JpaRepository, MemberCustomRepository { +public interface MemberRepository extends JpaRepository, MemberCustomRepository { + @Query("SELECT m FROM MemberEntity m LEFT JOIN FETCH m.department WHERE m.memberId = :id") + Optional findByIdWithFetchDepartment(Long id); + + @Query("SELECT m FROM MemberEntity m LEFT JOIN FETCH m.department WHERE m.memberId = :id AND m.status='ACTIVE'") + Optional findActiveMemberByIdWithFetchDepartment(Long id); + List findByRoleAndStatus(MemberRole role, MemberStatus status); Optional findByStatusAndMemberId(MemberStatus memberStatus, Long memberId); @@ -26,7 +34,9 @@ public interface MemberRepository extends JpaRepository, Me List findByIsReviewerTrue(); - List findAll(); // 전체 회원 조회 + @Query(value = "SELECT DISTINCT m FROM MemberEntity m LEFT JOIN FETCH m.department", + countQuery = "SELECT COUNT(DISTINCT m) FROM MemberEntity m") + Page findAllMembersWithFetchDepartment(Pageable pageable); Optional findByMemberIdAndIsReviewerTrue(Long memberId); diff --git a/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java b/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java index 10d072fb..23644738 100644 --- a/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java +++ b/src/main/java/clap/server/application/port/outbound/member/LoadMemberPort.java @@ -15,8 +15,12 @@ public interface LoadMemberPort { Optional findById(Long id); + Optional findByIdWithFetchDepartment(Long id); + Optional findActiveMemberById(Long id); + Optional findActiveMemberByIdWithFetchDepartment(Long id); + List findActiveManagers(); Optional findReviewerById(Long id); From 97b2dd58c98177910732474c6a8d75acc0684659 Mon Sep 17 00:00:00 2001 From: joowojr Date: Tue, 18 Feb 2025 12:03:03 +0900 Subject: [PATCH 3/6] =?UTF-8?q?CLAP-451=20Refactor:=20department=20fetch?= =?UTF-8?q?=20=EC=BF=BC=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #590 --- .../service/admin/DeleteMemberService.java | 2 -- .../service/admin/ManageMemberService.java | 11 +++++++---- .../service/member/MemberInfoService.java | 12 ++++++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/clap/server/application/service/admin/DeleteMemberService.java b/src/main/java/clap/server/application/service/admin/DeleteMemberService.java index 8888b7a8..046b2bd2 100644 --- a/src/main/java/clap/server/application/service/admin/DeleteMemberService.java +++ b/src/main/java/clap/server/application/service/admin/DeleteMemberService.java @@ -10,7 +10,6 @@ import clap.server.exception.ApplicationException; import clap.server.exception.code.MemberErrorCode; import lombok.RequiredArgsConstructor; -import org.hibernate.Hibernate; import org.springframework.transaction.annotation.Transactional; @ApplicationService @@ -29,7 +28,6 @@ public void deleteMember(Long memberId) { if (member.getMemberInfo().getRole() == MemberRole.ROLE_MANAGER) { managerInfoUpdatePolicy.validateNoRemainingTasks(member); } - Hibernate.initialize(member.getDepartment()); member.softDelete(); commandMemberPort.save(member); } diff --git a/src/main/java/clap/server/application/service/admin/ManageMemberService.java b/src/main/java/clap/server/application/service/admin/ManageMemberService.java index af6e7307..1ace0c6c 100644 --- a/src/main/java/clap/server/application/service/admin/ManageMemberService.java +++ b/src/main/java/clap/server/application/service/admin/ManageMemberService.java @@ -6,22 +6,23 @@ import clap.server.application.mapper.response.MemberResponseMapper; import clap.server.application.port.inbound.admin.MemberDetailUsecase; import clap.server.application.port.inbound.admin.UpdateMemberUsecase; -import clap.server.application.port.inbound.domain.MemberService; import clap.server.application.port.outbound.member.CommandMemberPort; import clap.server.application.port.outbound.member.LoadDepartmentPort; +import clap.server.application.port.outbound.member.LoadMemberPort; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Department; import clap.server.domain.model.member.Member; import clap.server.domain.policy.member.ManagerInfoUpdatePolicy; import clap.server.exception.ApplicationException; import clap.server.exception.code.DepartmentErrorCode; +import clap.server.exception.code.MemberErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @ApplicationService @RequiredArgsConstructor class ManageMemberService implements UpdateMemberUsecase, MemberDetailUsecase { - private final MemberService memberService; + private final LoadMemberPort loadMemberPort; private final CommandMemberPort commandMemberPort; private final LoadDepartmentPort loadDepartmentPort; private final ManagerInfoUpdatePolicy managerInfoUpdatePolicy; @@ -29,7 +30,8 @@ class ManageMemberService implements UpdateMemberUsecase, MemberDetailUsecase { @Override @Transactional public void updateMemberInfo(Long adminId, Long memberId, UpdateMemberRequest request) { - Member member = memberService.findById(memberId); + Member member = loadMemberPort.findByIdWithFetchDepartment(memberId).orElseThrow( + () -> new ApplicationException(MemberErrorCode.MEMBER_NOT_FOUND)); Department department = loadDepartmentPort.findById(request.departmentId()).orElseThrow(() -> new ApplicationException(DepartmentErrorCode.DEPARTMENT_NOT_FOUND)); managerInfoUpdatePolicy.validateDepartment(department, request.role()); @@ -46,7 +48,8 @@ public void updateMemberInfo(Long adminId, Long memberId, UpdateMemberRequest re @Override @Transactional(readOnly = true) public MemberDetailsResponse getMemberDetail(Long memberId) { - Member member = memberService.findById(memberId); + Member member = loadMemberPort.findByIdWithFetchDepartment(memberId).orElseThrow( + () -> new ApplicationException(MemberErrorCode.MEMBER_NOT_FOUND));; return MemberResponseMapper.toMemberDetailsResponse(member); } } diff --git a/src/main/java/clap/server/application/service/member/MemberInfoService.java b/src/main/java/clap/server/application/service/member/MemberInfoService.java index 36ea9175..4ebf3a2a 100644 --- a/src/main/java/clap/server/application/service/member/MemberInfoService.java +++ b/src/main/java/clap/server/application/service/member/MemberInfoService.java @@ -2,11 +2,13 @@ import clap.server.adapter.inbound.web.dto.member.response.MemberDetailInfoResponse; import clap.server.adapter.inbound.web.dto.member.response.MemberProfileResponse; -import clap.server.application.port.inbound.domain.MemberService; import clap.server.application.port.inbound.member.MemberDetailInfoUsecase; import clap.server.application.port.inbound.member.MemberProfileUsecase; +import clap.server.application.port.outbound.member.LoadMemberPort; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; +import clap.server.exception.ApplicationException; +import clap.server.exception.code.MemberErrorCode; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -16,19 +18,21 @@ @ApplicationService @RequiredArgsConstructor class MemberInfoService implements MemberProfileUsecase , MemberDetailInfoUsecase { - private final MemberService memberService; + private final LoadMemberPort loadMemberPort; @Override @Transactional(readOnly = true) public MemberProfileResponse getMemberProfile(Long memberId) { - Member member = memberService.findActiveMember(memberId); + Member member = loadMemberPort.findActiveMemberByIdWithFetchDepartment(memberId).orElseThrow( + () -> new ApplicationException(MemberErrorCode.MEMBER_NOT_FOUND)); return toMemberProfileResponse(member); } @Override @Transactional(readOnly = true) public MemberDetailInfoResponse getMemberInfo(Long memberId) { - Member member = memberService.findActiveMember(memberId); + Member member = loadMemberPort.findActiveMemberByIdWithFetchDepartment(memberId).orElseThrow( + () -> new ApplicationException(MemberErrorCode.MEMBER_NOT_FOUND)); return toMemberDetailInfoResponse(member); } } From af5ec800648354f1fce2af6b8e446596cc64ed49 Mon Sep 17 00:00:00 2001 From: joowojr Date: Tue, 18 Feb 2025 12:03:38 +0900 Subject: [PATCH 4/6] =?UTF-8?q?CLAP-451=20Docs:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20usecase=20deprecated?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #590 --- .../application/port/inbound/admin/FindAllMembersUsecase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/clap/server/application/port/inbound/admin/FindAllMembersUsecase.java b/src/main/java/clap/server/application/port/inbound/admin/FindAllMembersUsecase.java index b6ed4511..0a39025e 100644 --- a/src/main/java/clap/server/application/port/inbound/admin/FindAllMembersUsecase.java +++ b/src/main/java/clap/server/application/port/inbound/admin/FindAllMembersUsecase.java @@ -4,6 +4,7 @@ import clap.server.adapter.inbound.web.dto.common.PageResponse; import org.springframework.data.domain.Pageable; +@Deprecated public interface FindAllMembersUsecase { PageResponse findAllMembers(Pageable pageable); } From 9bb0a60653ca0dd16837f60effcb3f157bf31154 Mon Sep 17 00:00:00 2001 From: joowojr Date: Tue, 18 Feb 2025 12:08:29 +0900 Subject: [PATCH 5/6] =?UTF-8?q?CLAP-451=20Refactor:=20department=20mapper?= =?UTF-8?q?=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #590 --- .../outbound/persistense/mapper/MemberPersistenceMapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java b/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java index 3d877d51..460d2224 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/mapper/MemberPersistenceMapper.java @@ -11,7 +11,7 @@ import org.mapstruct.Named; import org.springframework.beans.factory.annotation.Autowired; -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", uses = DepartmentPersistenceMapper.class) public abstract class MemberPersistenceMapper { @Autowired From be2ebd24babcb6c327aa7ca1fc3f698908462013 Mon Sep 17 00:00:00 2001 From: joowojr Date: Tue, 18 Feb 2025 12:35:34 +0900 Subject: [PATCH 6/6] =?UTF-8?q?CLAP-451=20Refactor:=20=EC=9E=91=EC=97=85?= =?UTF-8?q?=20=EB=B3=B4=EB=93=9C=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=EC=97=90=20requester=20=EC=9D=98=20department=20fetch=20join?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #590 --- .../persistense/repository/task/TaskCustomRepositoryImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java index 397bd4b7..2042b1f8 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java @@ -117,6 +117,8 @@ public List findTasksByFilter(Long processorId, List sta BooleanBuilder builder = createTaskBoardFilter(processorId, statuses, untilDateTime, request); return queryFactory .selectFrom(taskEntity) + .leftJoin(taskEntity.requester).fetchJoin() + .leftJoin(taskEntity.requester.department).fetchJoin() .where(builder) .orderBy(taskEntity.processorOrder.asc()) .fetch();