Notice
Recent Posts
Recent Comments
Link
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

코딩 이래요래

코드잇 초급 프로젝트 개발 리포트 본문

JAVA/Project

코드잇 초급 프로젝트 개발 리포트

강범호 2025. 6. 11. 16:05

🧑‍💻 개발 리포트: [HRBanK]

1. 🗂 프로젝트 개요

이 프로젝트는 어떤 문제를 해결하기 위해 만들었는가?

핵심 기능은 무엇인가?

Batch로 데이터를 관리하는 Open EMS

  • 기업의 인적 자원을 안전하게 관리하는 서비스
  • 핵심 기능
    • 부서
      • 부서 등록, 조회, 수정, 삭제
    • 직원
      • 직원 등록, 조회, 수정, 삭제
    • 직원 정보 수정 이력
      • 직원 수정 정보 등록, 조회
    • 데이터 백업
      • 사용자에 의한 데이터 백업
      • 배치에 의한 데이터 백업
    • 파일
      • 사용자의 사진을 등록, 삭제
    • 대시보드
      • 총 직원 수
      • 최근 일주일 수정 이력 건수
      • 이번달 입사자 수
      • 마지막 백업 시간
      • 최근 1년 월별 직원수 변동 추이
      • 부서별 직원 분포
      • 직무별 직원 분포

2. 🧩 담당한 작업

내가 맡은 역할과 실제 구현한 부분은 무엇인가?

PM(매일 9시, 18시 회의 주관 및 전체적인 일정 관리)

ERD

프로젝트 배포

부서 관리

더보기

 

  • 부서 등록
    • 이름, 설명, 설립일을 입력하여 부서를 등록할 수 있음
      • 이름은 중복될 수 없음
  • 부서 수정
    • 이름, 설명, 설립일을 수정할 수 있음
      • 이름은 중복될 수 없음
  • 부서 목록 조회
    • 이름 or 설명으로 부서 목록을 조회할 수 있음
      • 이름 or 설명은 부분 일치 조건
      • 조회 조건이 여러 개인 경우 모든 조건을 만족한 결과로 조회
    • 이름, 설립일로 정렬 및 페이지네이션을 구현
      • 여러 개의 정렬 조건 중 선택적으로 1개의 정렬 조건만 가질 수 있음
  • 부서 삭제
    • 소속된 직원이 없는 경우에만 부서를 삭제할 수 있음

3. 🔧 기술적 성과

무슨 기술을 사용했고, 어떤 기능을 실제로 구현했는가?

Skill

  • Spring Boot 기반 REST API 구현
  • Spring Data JPA를 활용한 DB 연동 및 동적 쿼리 구성
  • QueryDSL을 사용한 복잡한 쿼리 작성
  • PostgreSQL을 사용하여 데이터베이스 구성
  • H2를 사용하여 로컬 개발 데이터베이스 구축
  • 부서 등록

  • 이미 존재하는 부서명일 경우 예외 처리
  • 생성한 부서와 해당 부서의 소속된 직원 수를 반환

  • 부서 수정

  • 부서 수정 시 이미 존재 하는 부서명일 경우 예외 처리

  • 부서 삭제

  • 삭제할 부서의 소속된 직원이 없을 경우에만 삭제를 진행
  • 만약 소속된 직원이 존재하면 예외 처리
  • 만약 존재하지 않는 부서 ID일 경우 예외 처리

  • 부서 목록 조회

  • pageNumber를 표시하지 않아도 되기 때문에 cursor 기반 페이지네이션 구성
  • BooleanBuilder를 사용하여 동적 쿼리 생성
  • QueryDSL을 사용해 코드 가독성 향상
@Override
public List<Department> findByCursorCondition(String keyword, Long lastId, String cursorValue,
                                              int size, String sortField, String sortDirection) {
    QDepartment d = QDepartment.department;

    BooleanBuilder builder = new BooleanBuilder();

// 키워드 조건if (keyword != null && !keyword.isBlank()) {
        builder.and(d.name.containsIgnoreCase(keyword)
                .or(d.description.containsIgnoreCase(keyword)));
    }

// 커서 조건if (cursorValue != null && lastId != null) {
        if ("name".equals(sortField)) {
            builder.and(d.name.gt(cursorValue)
                    .or(d.name.eq(cursorValue).and(d.id.gt(lastId))));
            if ("desc".equalsIgnoreCase(sortDirection)) {
                builder = new BooleanBuilder()
                        .and(d.name.lt(cursorValue)
                                .or(d.name.eq(cursorValue).and(d.id.lt(lastId))));
            }
        } else if ("establishedDate".equals(sortField)) {
            LocalDate parsed = LocalDate.parse(cursorValue);
            builder.and(d.establishedDate.gt(parsed)
                    .or(d.establishedDate.eq(parsed).and(d.id.gt(lastId))));
            if ("desc".equalsIgnoreCase(sortDirection)) {
                builder = new BooleanBuilder()
                        .and(d.establishedDate.lt(parsed)
                                .or(d.establishedDate.eq(parsed).and(d.id.lt(lastId))));
            }
        }
    }

// 정렬
    Order direction = "desc".equalsIgnoreCase(sortDirection) ? Order.DESC : Order.ASC;
    OrderSpecifier<?> orderSpecifier1;
    if ("name".equals(sortField)) {
        orderSpecifier1 = new OrderSpecifier<>(direction, d.name);
    } else if ("establishedDate".equals(sortField)) {
        orderSpecifier1 = new OrderSpecifier<>(direction, d.establishedDate);
    } else {
        orderSpecifier1 = new OrderSpecifier<>(direction, d.id);
    }

    return queryFactory.selectFrom(d)
            .where(builder)
            .orderBy(orderSpecifier1, new OrderSpecifier<>(direction, d.id))
            .limit(size + 1)
            .fetch();
}
  • 커서 기반 목록 조회 응답 DTO 통합

  • 모든 커서 기반 목록 조회 API는 content를 제외한 모든 필드를 공통적으로 반환
  • 제네릭 문법을 사용하여 공통 응답 CursorPageResponse<T>로 통합

4. 🧨 문제점 및 해결 과정

진짜 고생했던 부분이 있다면?

기술적으로 어떤 시도와 해결을 했는가?

  • Railway 배포

  • 상황 : Railway에 Springboot(main 브랜치)와 PostgreSQL 인스턴스를 생성 후 Springboot에 PostgreSQL을 연결하는 과정
  • 문제 : Springboot 빌드 시 데이터 소스를 변수로 인식하는게 아닌 문자열로 인식하는 상황
  • application.yaml
spring:
  datasource:
	  url: ${DATABASE_URL}
		username: ${DATABASE_USER}
		password: ${DATABASE_PASSWORD}
  • 해결 : Springboot 인스턴스에 등록된 변수명과 PostgrSQL에 등록된 변수명이 달라 인식을 못하였음
  • Springboot
    • ${DATABASE_URL}
    • ${DATABASE_USER}
    • ${DATABASE_PASSWORD}
  • PostgreSQL
    • ${POSTGRES_URL}
    • ${POSTGRES_USER}
    • ${POSTGRES_PASSWORD}

  • ERD 부서↔직원↔수정 이력 참조 관계

  • 상황 : 직원을 삭제할 때, 수정 로그를 같이 생성해야하는데, 초기 ERD는 수정 로그 테이블에서 직원이 id값을 외래키로 참조하고 있어 외래키 참조 문제가 발생하는 상황
  • 과제 : 직원을 삭제할 때, 수정 로그를 같이 생성하고 직원은 삭제되어야 함
  • 행동 :
    • 직원 soft-delete 전략
      • 직원 테이블에 deleted 속성을 추가하여 true, false로 관리
      • 외래키 참조 무결성 문제는 해결되었지만, 실제로 삭제 되는 직원이 없기에 부서 삭제가 불가능한 문제 발생
    • 수정 로그 테이블 외래키 제거

  • 수정 로그 테이블에 employee_id 외래키를 제거하여 직원 삭제가 가능하게 하였고, 수정 로그 데이터는 그대로 유지되어 수정 기록은 보존할 수 있게 되었음

5. 🤝 협업 및 피드백

팀원과의 협업은 어땠고, 어떤 피드백이 오갔는가?

  • 프로젝트를 진행하면서 문제 발생 시 디스코드로 즉시 공유하고, 팀원들과 함께 해결책을 찾아 바로바로 이슈를 해결했음
  • 코드 리뷰를 진행하면서 더 좋은 방향으로 개선할 수 있을 것 같은 코드는 제안하는 방법으로 리뷰 진행
 

[직원 정보 수정 이력 관리] 이력 등록 기능 구현 by adjoon1 · Pull Request #9 · sb3-HRBANK-team4/sb3-hrbank-te

📌 작업 개요 이력 등록 기능 구현 사번 기반으로 직원 정보를 조회해 변경 이력을 저장할 수 있도록 설계 🔧 주요 작업 내용 ChangelogCreateRequestDto 생성: 직원 정보 수정 이력 등록 DTO 작성 ChangeLo

github.com

 

6. 🧼 코드 품질 및 최적화

가독성, 유지보수, 성능 측면에서 어떤 점을 신경 썼는가?

  • 팀원들과 협업을 위해 각 클래스와 메소드에 javadoc주석을 이용해 의사소통 및 가독성 향상
  • ErrorResponse로 에러 응답을 표준화하고, CustomException을 통해 정확하고 자세한 에러 메세지 반환

7. 🚀 향후 개선 사항 및 제안

다음에 다시 한다면 이렇게 할 것이다!

지금은 미뤘지만 개선하고 싶은 부분은?

  • 커서 기반 페이지네이션으로 부서 목록을 조회하여 반환하지만 테스트 시 대량의 데이터(1,000개 이상)으로 테스트를 해보지 않았는데, 만약 다시 커서 기반 페이지네이션을 구현할 일이 생기면 대량의 데이터를 테스트하는 과정에서 성능과 안정성을 개선해보고 싶다는 생각이 들었음