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/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..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 @@ -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", uses = DepartmentPersistenceMapper.class) 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; 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/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(); 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); } 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); 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); } }