Skip to content

areyouhun/emp-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

인프런 워밍업 클럽 - 미니 프로젝트

인프런 워밍업 클럽 0기 (BE)에서 진행하는 미니 프로젝트입니다.


요구사항

📌 기술 스택

  • JDK 17
  • Spring Boot 3.x.x
  • JPA
  • MySQL

📌 기능 구현

  • 팀 등록: 회사 팀을 등록할 수 있어야 한다. 필수 정보는 다음과 같다.
    • 팀 이름
  • 직원 등록: 직원을 등록할 수 있어야 한다. 필수 정보는 다음과 같다.
    • 직원 이름
    • 매니저 여부
    • 입사 일자
    • 생일
  • 팀 조회: 모든 팀의 정보를 한 번에 조회할 수 있어야 한다.
    • name: 팀 이름
    • manager: 팀 매니저 (없으면 null)
    • memberCount: 팀 인원 수
      [
        {
          "name": "경영지원",
          "manager": "민병관",
          "memberCount": 2
        }, ...
      ]
  • 직원 조회: 모든 직원의 정보를 한 눈에 조회할 수 있어야 한다.
    • name: 직원 이름
    • teamName: 소속 팀 이름
    • role: 매니저인지 직원인지
    • birthday: 생일
    • workStartDate: 입사일
      [
        {
          "name": "강호식",
          "teamName": "경영지원",
          "role": "MEMBER"
          "birthday": "2000-01-01",
          "workStartDate": "2024-01-01"
        }, ...
      ]

풀이

⚙ 프로젝트 생성

스프링 이니셜라이저에서 스프링 프로젝트를 생성한다.

  • Project: Gradle - Groovy
  • Language: Java
  • Spring Boot: 3.1.9
  • Project Metadata
    • Group: club.warmingup
    • Artifact: emp-app
    • Name: emp-app
    • Description: practice
    • Package-name: club.warmingup.emp-app
  • Packaging: Jar
  • Java: 17
  • Dependencies: Spring Web / Lombok / Spring Data JPA / MySQL Driver

⚙ DB 설계

create table team (
    team_id bigint auto_increment,
    name varchar(20) not null unique,
    primary key (team_id)
);
create table employee (
    emp_id bigint auto_increment,
    name varchar(50) not null,
    team_id bigint not null,
    role varchar(50) not null check (role in ('MANAGER', 'MEMBER')),
    work_start_date datetime not null default (current_time),
    birthday datetime not null default '2000-01-01',
    primary key (emp_id),
    FOREIGN KEY (team_id) REFERENCES team (team_id)
);


⚙ 도메인 개발

[팀 엔티티]

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long teamId;
@Column(nullable = false, name = "name")
private String name;
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
public Team(String name) {
this.name = name;
}
public int countEmployees() {
return employees.size();
}
public List<Employee> getEmployees() {
return Collections.unmodifiableList(employees);
}
}


[직원 엔티티]

@Entity
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long empId;
@Column(nullable = false, name = "name")
private String name;
@ManyToOne
@JoinColumn(nullable = false, name = "team_id")
private Team team;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Role role;
@Column(nullable = false)
private LocalDate workStartDate;
@Column(nullable = false)
private LocalDate birthday;
public Employee(String name, Team team, Role role, LocalDate workStartDate, LocalDate birthday) {
this.name = name;
this.team = team;
this.role = role;
this.workStartDate = workStartDate;
this.birthday = birthday;
}
}
팀과 직원은 DB에서 1:N의 연관관계를 맺고 있기에, JPA 기술을 이용해 반영했다.


[ENUM - 직책]

public enum Role {
MANAGER,
MEMBER
}

jackson:
mapper:
accept-case-insensitive-enums: true
요청 데이터를 ENUM 데이터로 바인딩하는 과정에서 대소문자를 구분하지 않도록 application.yml 파일에 관련 옵션을 설정한다.


🚀 기능 | 팀 등록 & 조회

[팀 컨트롤러]

@RestController
@RequiredArgsConstructor
public class TeamController {
private final TeamService teamService;
@PostMapping("/team")
public void saveTeam(@RequestParam String name) {
teamService.save(name);
}
@GetMapping("/teams")
public List<TeamResponse> getTeams() {
return teamService.findAll();
}
}
입력값 유효성 검사는 제외했다.


[팀 서비스]

@Service
@RequiredArgsConstructor
public class TeamService {
private final TeamRepository teamRepository;
@Transactional
public void save(String name) {
if (teamRepository.existsByName(name)) {
throw new IllegalArgumentException("이미 존재하는 팀 이름입니다.");
}
teamRepository.save(new Team(name));
}
@Transactional(readOnly = true)
public List<TeamResponse> findAll() {
return teamRepository.findAll().stream()
.map(TeamResponse::new)
.collect(Collectors.toList());
}
}

  • 팀 등록: 팀 이름의 중복 여부 검사
  • 팀 조회: 엔티티 안에 담긴 데이터를 응답용 DTO로 전달

[팀 레포지토리]

public interface TeamRepository extends JpaRepository<Team, Long> {
boolean existsByName(String name);
Optional<Team> findByName(String name);
}


[팀 응답용 DTO]

@Data
public class TeamResponse {
private String name;
private String manager;
private int memberCount;
public TeamResponse(Team team) {
this.name = team.getName();
this.manager = getManager(team);
this.memberCount = team.countEmployees();
}
private String getManager(Team team) {
Employee manager = team.getEmployees().stream()
.filter(employee -> employee.getRole() == Role.MANAGER)
.findFirst()
.orElse(null);
return manager != null ? manager.getName() : null;
}
}


팀 등록 결과 (성공)

팀 등록 결과 (실패)

팀 조회 결과


🚀 기능 | 직원 등록 & 조회

[직원 컨트롤러]

@RestController
@RequiredArgsConstructor
public class EmployeeController {
private final EmployeeService employeeService;
@PostMapping("/employee")
public void saveEmployee(@RequestBody EmployeeCreateRequest request) {
employeeService.save(request);
}
@GetMapping("/employees")
public List<EmployeeResponse> getTeams() {
return employeeService.findAll();
}
}
입력값 유효성 검사는 제외했다.


[직원 서비스]

@Service
@RequiredArgsConstructor
public class EmployeeService {
private final EmployeeRepository employeeRepository;
private final TeamRepository teamRepository;
@Transactional
public void save(EmployeeCreateRequest request) {
Team team = teamRepository.findByName(request.getTeamName())
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 팀입니다."));
Employee employee = new Employee(request.getName(),
team,
request.getRole(),
request.getWorkStartDate(),
request.getBirthday());
employeeRepository.save(employee);
}
@Transactional(readOnly = true)
public List<EmployeeResponse> findAll() {
return employeeRepository.findAll().stream()
.map(EmployeeResponse::new)
.collect(Collectors.toList());
}
}

  • 직원 등록: 존재하는 팀인지 검사한다. 검사를 통과하면 요청용 DTO 안에 담긴 데이터를 갖고 직원 엔티티를 생성해 직원 레포지토리에 추가한다.
  • 직원 조회: 엔티티 안에 담긴 데이터를 응답용 DTO로 전달

[직원 레포지토리]

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}


[직원 요청용 DTO]

@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmployeeCreateRequest {
private String name;
private String teamName;
private Role role;
private LocalDate workStartDate;
private LocalDate birthday;
}


[직원 응답용 DTO]

@Data
public class EmployeeResponse {
private String name;
private String teamName;
private Role role;
private LocalDate workStartDate;
private LocalDate birthday;
public EmployeeResponse(Employee employee) {
this.name = employee.getName();
this.teamName = employee.getTeam().getName();
this.role = employee.getRole();
this.workStartDate = employee.getWorkStartDate();
this.birthday = employee.getBirthday();
}
}


직원 등록 결과 (실패)

직원 등록 결과 (성공)

직원 조회 결과

Releases

No releases published

Packages

No packages published

Languages