개요
앞에서 설계한 api 명세서와 erd를 가지고 개인 과제 구현을 해보자.
api 명세서는 postman으로 다시 만들었다.
스케쥴
매니져
패키지
패키지 구조는 도메인 기준으로 만들었다. 원래 처음에는 service가 있다고 치면 안에 schedule서비스 manager서비스 이렇게 뒀는데 튜터님께 조언을 받고 변경을 하였다. 조금 더 깔끔해진 것 같다.
Common에는 공용으로 사용할 익셉션, 인터페이스들을 모아두도록 하였다.
Dto
각 도메인별 dto를 만들어주었다. dto 종류는 두 가지로 나누었다. reqeust, response
ScheduleRequestDto
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class ScheduleRequestDto {
@NotNull
@Positive
private Long managerId;
@NotBlank
@Size(min = 1, max = 30)
private String password;
@NotBlank
@Size(min = 1, max = 200)
private String contents;
}
ScheduleResponseDto
@Getter
@Builder
@AllArgsConstructor
public class ScheduleResponseDto {
private Long scheduleId;
private Long managerId;
private String password;
private String contents;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}
레포지토리 클래스
명세서 api를 보고 필요한 기능들을 구현하였다.
- Repository: 최상위 인터페이스
- 제네릭을 활용해서 Key와 Entity타입을 받도록 설계
- 공통으로 들어가는 기본적인 CRUD 메서드 구현하도록 강제
- ScheduleRepository: 인터페이스
- Repository를 상속
- 스케쥴 저장소에 필요한 메서드 추가
- ScheduleRepositoryImpl: 구현 클래스
- ScheduleRepository를 상속받아서 메서드 구현
JDBC를 사용하다 보니 쿼리문을 구현 클래스에서 만들어줬는데 리팩토링 해서 ScheduleRepositorySQL이라는 클래스에서 쿼리문을 받아오도록 변경하였다.
ScheduleRepositorySQL
@Component
public class ScheduleRepositorySQL {
public String save() {
return "INSERT INTO schedules (manager_id, password, contents, created_time, updated_time) VALUES(?,?,?,?,?)";
}
public String update() {
return "UPDATE schedules SET manager_id = ?, password = ?, contents = ?, created_time = ?, updated_time = ? where schedule_id = ?";
}
public String findById() {
return "SELECT * FROM schedules WHERE schedule_id = ?";
}
public String findAll() {
return "SELECT * FROM schedules";
}
public String findByPage() {
String query = "select * from schedules as s join (select schedule_id from schedules limit ? offset ?) as t on s.schedule_id = t.schedule_id";
return query;
}
public String findAllByFilter(String updatedTime, Long managerId) {
String sql = "select * from schedules";
if (updatedTime != null && managerId == null) {
sql += MessageFormat.format(
" where DATE_FORMAT(updated_time, ''%Y-%m-%d'') in (''{0}'')",
updatedTime);
} else if (updatedTime == null && managerId != null) {
sql += " where manager_id = " + managerId;
} else if (updatedTime != null && managerId != null) {
sql +=
MessageFormat.format(" where DATE_FORMAT(updated_time, ''%Y-%m-%d'') in (''{0}'')",
updatedTime) + " and manager_id = " + managerId;
}
sql += " order by updated_time desc";
return sql;
}
public String delete() {
return "DELETE FROM schedules WHERE schedule_id = ?";
}
}
Repository
package com.sparta.springasignment.common.interfaces;
import java.util.List;
import java.util.Optional;
public interface Repository<E extends Entity, Key> {
Key save(E entity);
void update(E entity);
void delete(E entity);
Optional<E> findById(Key id);
List<E> findAll();
}
ScheduleRepository
package com.sparta.springasignment.schedule.repository;
import com.sparta.springasignment.common.interfaces.Repository;
import com.sparta.springasignment.schedule.entity.Schedule;
import java.util.List;
public interface ScheduleRepository extends Repository<Schedule, Long> {
List<Schedule> findAllByPage(Integer page, Integer size);
List<Schedule> findAllByFilter(String updatedTime, Long managerId);
}
ScheduleRepositoryImpl
package com.sparta.springasignment.schedule.repository;
import com.sparta.springasignment.schedule.entity.Schedule;
import com.sparta.springasignment.schedule.repository.rowmapper.ScheduleRowMapper;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
@Repository
@RequiredArgsConstructor
public class ScheduleRepositoryImpl implements ScheduleRepository {
private final JdbcTemplate jdbcTemplate;
private final ScheduleRepositorySQL sql;
private final ScheduleRowMapper rowMapper;
private final KeyHolder keyHolder = new GeneratedKeyHolder();
@Override
public Long save(Schedule schedule) {
jdbcTemplate.update(con -> {
PreparedStatement preparedStatement = con.prepareStatement(sql.save(),
Statement.RETURN_GENERATED_KEYS);
preparedStatement.setLong(1, schedule.getManagerId());
preparedStatement.setString(2, schedule.getPassword());
preparedStatement.setString(3, schedule.getContents());
preparedStatement.setTimestamp(4, Timestamp.valueOf(schedule.getCreatedTime()));
preparedStatement.setTimestamp(5, Timestamp.valueOf(schedule.getUpdatedTime()));
return preparedStatement;
}, keyHolder);
return Objects.requireNonNull(keyHolder.getKey()).longValue();
}
@Override
public void update(Schedule schedule) {
jdbcTemplate.update(sql.update(), schedule.getManagerId(), schedule.getPassword(),
schedule.getContents(), schedule.getCreatedTime(), schedule.getUpdatedTime(),
schedule.getScheduleId());
}
@Override
public void delete(Schedule schedule) {
jdbcTemplate.update(sql.delete(), schedule.getScheduleId());
}
@Override
public Optional<Schedule> findById(Long scheduleId) {
try {
Schedule schedule = jdbcTemplate.queryForObject(sql.findById(), rowMapper, scheduleId);
return Optional.ofNullable(schedule);
} catch (DataAccessException e) {
return Optional.empty();
}
}
@Override
public List<Schedule> findAll() {
try {
return jdbcTemplate.query(sql.findAll(), rowMapper);
} catch (DataAccessException e) {
return new ArrayList<>();
}
}
@Override
public List<Schedule> findAllByPage(Integer page, Integer size) {
try {
int limit = size;
int offset = (page - 1) * size;
return jdbcTemplate.query(sql.findByPage(), rowMapper, limit, offset);
} catch (DataAccessException e) {
return new ArrayList<>();
}
}
@Override
public List<Schedule> findAllByFilter(String updatedTime, Long managerId) {
try {
return jdbcTemplate.query(sql.findAllByFilter(updatedTime, managerId), rowMapper);
} catch (DataAccessException e) {
return new ArrayList<>();
}
}
}
JDBC 템플릿을 사용해서 sql을 실행할 때 rowmapper가 필요해서 따로 ScheduleRowMapper 클래스를 만들어주었다.
ScheduleRowMapper
package com.sparta.springasignment.schedule.repository.rowmapper;
import com.sparta.springasignment.schedule.entity.Schedule;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
@Component
public class ScheduleRowMapper implements RowMapper<Schedule> {
@Override
public Schedule mapRow(ResultSet rs, int rowNum) throws SQLException {
return Schedule.builder()
.scheduleId(rs.getLong("schedule_id"))
.managerId(rs.getLong("manager_id"))
.password(rs.getString("password"))
.contents(rs.getString("contents"))
.createdTime(rs.getTimestamp("created_time").toLocalDateTime())
.updatedTime(rs.getTimestamp("updated_time").toLocalDateTime())
.build();
}
}
Entity
기존의 Entity 클래스는 아래와 같았다.
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Schedule {
@Setter
private Long scheduleId;
@Setter
private Long managerId;
private String password;
@Setter
private String contents;
private LocalDateTime createdTime;
@Setter
private LocalDateTime updatedTime;
}
Repository에서 Entity 타입을 정해주기 때문에 마커 인터페이스를 활용해서 빈 Entity 인터페이스를 만들어주고 Entity 클래스들은 Entity인터페이스를 상속받게 하였다.
변경 후
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Schedule implements Entity {
@Setter
private Long scheduleId;
@Setter
private Long managerId;
private String password;
@Setter
private String contents;
private LocalDateTime createdTime;
@Setter
private LocalDateTime updatedTime;
}
느낀점
유튜브로 연습 프로젝트를 따라 했을 때 JPA를 썼엇는데, JPA처럼 한 번 구조를 잡고 구현해보고 싶어서 비슷하게 따라했다. 인터페이스를 사용하면서 조금 더 객체지향 설계에 대해 알아가고 있는 것 같다. 튜터님께 코드 피드백을 받았는데 모객체를 생성하고 초기화할 때 생성자와 세터말고 @Builder도 사용해보라고 하셨다. 한눈에 보기에도 되게 가독성이 좋아보여서 바로 수정했다. 또한, Setter도 신중하게 사용하라고 하셔서 가슴에 새겨들었다.
'부트캠프 > Dev' 카테고리의 다른 글
필터에서 예외처리 (0) | 2024.08.20 |
---|---|
개인과제 구현 -2 (0) | 2024.08.16 |
스프링 입문 개인 프로젝트 시작 (0) | 2024.08.11 |
캠프 프로그램 팀프로젝트 회고록 (0) | 2024.08.08 |
오늘의 팀활동 (0) | 2024.08.05 |