인프런 워밍업 클럽 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
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 기술을 이용해 반영했다.
|
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); |
|
} |
|
@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> { |
|
} |
|
@Data |
|
@NoArgsConstructor |
|
@AllArgsConstructor |
|
public class EmployeeCreateRequest { |
|
private String name; |
|
private String teamName; |
|
private Role role; |
|
private LocalDate workStartDate; |
|
private LocalDate birthday; |
|
} |
|
@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(); |
|
} |
|
} |