Spring Calendar

SJ.CHO·2024년 9월 30일

개발 공통사항

Lv.1

필수기능

사용기술

  • MySQL
  • DataBase

진행과정

1. API 명세서 작성

API 명세서 링크(클릭)

2. ERD 작성하기

  • Lv.1~3 까지의 과정은 별도의 처리가 없기에 event Table 한개만으로 정보 처리.

3. SQL 작성하기

Create TABLE IF NOT EXISTS EVENT(
    id int PRIMARY KEY AUTO_INCREMENT,
    todo varchar(500) NOT NULL ,
    password varchar(20) NOT NULL ,
    createddate datetime NOT NULL ,
    modifieddate datetime NOT NULL ,
    startday date NOT NULL ,
    endday date NOT NULL ,
    creator varchar(5) NOT NULL
);
  • NULL 사용을 최대한 지양하기 위한 NOTNULL 키워드 사용.
  • id : 일정들을 관리할 고유값 AUTO_INCREMENT 속성을 주어 자동으로 증가하게끔 생성.
  • todo : 일정에 대한 내용 작성란
  • password : 작성자외의 유저가 데이터삭제를 방지하기위한 보안책
  • createddate : 일정 작성일
  • modifieddate : 일정 수정일
  • startday : 일정 시작일
  • endday : 일정 종료일
  • creator : 작성자 이름

Lv.2

필수기능

사용기술

  • MySQL
  • DataBase
  • Spring Boot
  • Java
  • JDBC

1. 일정 생성

  • 컨트롤러
    @PostMapping("/schedul/")
    public EventResponseDto createEvent(@RequestBody @Valid EventRequestDto requestDto) {
        return eventService.createEvent(requestDto);
    }
  • 서비스 클래스
    // 일정 생성 메소드.
    public EventResponseDto createEvent(EventRequestDto requestDto) {
        User userinfo = userRepository.findId(requestDto.getUser_id());
        // 해당 유저가 존재하지 않는 경우
        if (userinfo != null) {
            // 해당 일자가 유효한지 검사 (Ex : 2024-02-30(false))
            if (formatCheck(String.valueOf(requestDto.getStartday())) && formatCheck(String.valueOf(requestDto.getEndday()))) {
                // 일정종료일이 시작일 보다 빠를경우 (Ex : 2024-09-30 ~ 2024-08-30)
                if (requestDto.getStartday().before(requestDto.getEndday())) {
                    // RequestDto -> Entity
                    Event event = new Event(requestDto);
                    // DB 저장
                    Event saveEvent = eventRepository.save(event, userinfo);
                    // Entity -> ResponseDto
                    return new EventResponseDto(saveEvent);
                } else {
                    throw new CustomException(END_DATE_BEFORE_START_DATE);
                }
            } else {
                throw new CustomException(DATE_PARSE_ERROR);
            }
        } else {
            throw new CustomException(USER_NOT_FOUND);
        }
    }
  • 레파지토리 클래스
    public Event save(Event event, User userinfo) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        // SQL 문 작성
        String sql = "INSERT INTO event (todo, password,createddate,modifieddate,startday,endday,user_id,username) VALUES (?,?,?,?,?,?,?,?)";
        java.sql.Timestamp timeNow = Timestamp.valueOf(LocalDateTime.now());
        jdbcTemplate.update(con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);
                    // Event 객체 필드에 있는 정보 주입.
                    preparedStatement.setString(1, event.getTodo());
                    preparedStatement.setString(2, event.getPassword());
                    preparedStatement.setString(3, String.valueOf(timeNow));
                    preparedStatement.setString(4, String.valueOf(timeNow));
                    preparedStatement.setString(5, event.getStartday().toString());
                    preparedStatement.setString(6, event.getEndday().toString());
                    preparedStatement.setString(7, event.getUser_id().toString());
                    preparedStatement.setString(8, userinfo.getUsername());
                    return preparedStatement;
                },
                keyHolder);
        // DB Insert 후 받아온 기본키 및 생성/수정시간 삽입
        Long id = keyHolder.getKey().longValue();
        event.setId(id);
        event.setCreateddate(timeNow);
        event.setModifieddate(timeNow);
        event.setUsername(userinfo.getUsername());
        return event;
    }
  • 요구 조건 :

    • 할일, 작성자명, 비밀번호, 작성/수정일을 저장
    • 작성/수정일은 날짜와 시간을 모두 포함한 형태
    • 각 일정은 고유 ID 를 자동생성 관리
    • 최초 입력 작성/수정일은 동일함.
  • 3 Layer 구조 설계로 각계층의 책임을 가지고 설계.

  • 요청을 DTO에 담아서 Entity 화 후 Repository에게 전달하면 Repository는 해당 객체를 가지고 쿼리문을 작성, 전송하고 전송한 객체를 반환한다.

  • 생성/수정일은 초기값은 동일하기에 메소드 진입시 현재시간을 저장.

  • 일정의 시작/종료일을 추가하였으며, 종료일이 시작일보다 빠를경우 에러발생.

  • 입력 값 중 NULL 이 존재할경우 에러발생.

  • 성공적 입력

  • DB 입력 형태

  • NULL 값이 존재할 경우 예외

  • 일정 종료일이 시작일보다 빠를경우 예외

  • 서버 에러로그

2-1. 일정 조회 (전체)

  • 컨트롤러
    // 일정을 조회하는 컨트롤러 메소드 정보 기입여부에 따른 출력이 달라짐.
    @GetMapping("/schedul/")
    public List<EventResponseDto> getAllEvents(@RequestParam(required = false) String creator, @RequestParam(required = false) String modifieddate) {
        return eventService.getEventAll(creator, modifieddate);
    }
  • 서비스
    // 일정 조회 메소드. 파라미터 여부에 따른 결과가 달라짐.
    public List<EventResponseDto> getEventAll(String creator, String modifieddate) {
        return eventRepository.findAll(creator, modifieddate);
    }
  • 레파지토리
    // Client 에서 보내온 파라미터값에 따른 DB 조회 메소드
    // 조건에 따른 WHERE 절 변화
    public List<EventResponseDto> findAll(String creator, String modifieddate) {
        // 둘다 기입하지 않았을시 모든 일정 조회
        String sql = "SELECT * FROM event";
        // 작성자, 수정일을 둘다 기입했을시.
        if (creator != null && modifieddate != null) {
            sql += " WHERE creator = " + "'" + creator + "'" + " OR date_format(modifieddate,'%Y-%m-%d') = " + "'" + modifieddate + "'";
        }
        // 수정일만 기입시 수정일 기준으로 DB 조회.
        else if (modifieddate != null) {
            sql += " WHERE date_format(modifieddate,'%Y-%m-%d') = " + "'" + modifieddate + "'";
        }
        // 작성자만 기입시 작성자 기준으로 DB 조회.
        else if (creator != null) {
            sql += " WHERE creator = " + "'" + creator + "'";
        }
        sql += " ORDER BY modifieddate DESC";
        return jdbcTemplate.query(sql, new RowMapper<EventResponseDto>() {
            @Override
            public EventResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                // SQL 의 결과로 받아온 Memo 데이터들을 EventResponseDto 타입으로 변환해줄 메서드
                Long id = rs.getLong("id");
                String todo = rs.getString("todo");
                java.sql.Timestamp createddate = rs.getTimestamp("createddate");
                java.sql.Timestamp modifieddate = rs.getTimestamp("modifieddate");
                java.sql.Date startday = rs.getDate("startday");
                java.sql.Date endday = rs.getDate("endday");
                String creator = rs.getString("creator");
                return new EventResponseDto(id, todo, createddate, modifieddate, startday, endday, creator);
            }
        });
    }
  • 요구 조건 :

    • 수정일 (YYYY-MM-DD), 작성자명 을 이용한 일정목록 조회
      해당조건은 둘 다 충족, 하나만 충족, 둘 다 미충족 이 가능하다.
    • 수정일 을 기준으로 내림차순 조회.
  • @RequestParam(required = false) 기능을 이용하여 필수요소로 사용을 억제.

  • 들어온 값 (작성일 OR 수정일) 에 따라 SQL문의 WHERE 절을 나눠서 사용.

  • 모두조회 (값 없음)

  • 모두조회 (작성자명 개발자1 혹은 2024-09-30에 수정데이터)

2-2. 일정 조회 (단건)

  • 컨트롤러
    // ID 값을 기준으로 정보를 조회하는 컨트롤러 메소드.
    @GetMapping("/schedul/id/")
    public EventResponseDto getEventById(@RequestParam Long id) {
        return eventService.getEventById(id);
    }
  • 서비스 클래스
    // 해당 일정 ID의 정보 조회 메소드.
    public EventResponseDto getEventById(Long id) {
        Event event = eventRepository.findId(id);
        if (event != null) {
            return new EventResponseDto(event);
        } else {
            throw new CustomException(OUT_OF_RANGE);
        }
    }
  • 레파지토리
    // ID 값을 기준으로 DB 조회 메소드
    public Event findId(Long id) {
        String sql = "SELECT * FROM event WHERE id=?";
        return jdbcTemplate.query(sql, resultSet -> {
            if (resultSet.next()) {
                Event event = new Event();
                event.setId(resultSet.getLong("id"));
                event.setTodo(resultSet.getString("todo"));
                event.setPassword(resultSet.getString("password"));
                event.setCreateddate(resultSet.getTimestamp("createddate"));
                event.setModifieddate(resultSet.getTimestamp("modifieddate"));
                event.setStartday(resultSet.getDate("startday"));
                event.setEndday(resultSet.getDate("endday"));
                event.setCreator(resultSet.getString("creator"));
                return event;
            } else {
                return null;
            }
        }, id);
    }
  • 요구 조건 :

    • 일정의 고유 식별자(ID)를 사용하여 조회합니다.
  • ID 의 값은 필수이기에 ID값을 받고 DB내의 해당 ID를 가지는 일정ROW가 존재하는지 검사.

  • 존재하지 않는다면 throw new CustomException(OUT_OF_RANGE); 을 통해 커스텀 예외를 발생시킨다.

  • 조회성공

  • 조회실패

Lv.3

필수기능

사용기술

  • MySQL
  • DataBase
  • Spring Boot
  • Java
  • JDBC

1. 일정 수정

  • 컨트롤러
    // ID 값을 기준으로 정보를 수정하는 컨트롤러 메소드
    // DTO 를 Body 로 받는 이유는 URL 이 아닌 Body 로 받으면서 보안성 향상.
    @PutMapping("/schedul/")
    public Long updateEvent(@RequestParam Long id, @RequestBody @Valid EventRequestDto requestDto) {
        return eventService.updateEvent(id, requestDto);
    }
  • 서비스 클래스
    public Long updateEvent(Long id, EventRequestDto requestDto) {
        Event event = eventRepository.findId(id);
        // 글을 작성한 유저가 접근했는지 검사하기위한 UserInfo 객체 생성
        User userinfo = userRepository.findId(requestDto.getUser_id());
        // 해당 ID 일정이 존재하는 경우
        if (event != null || userinfo != null) {
            // 접근 User 가 일정을 작성한 유저 이면서 게시글에 대한 비밀번호가 일치하는지?
            if (requestDto.getPassword().equals(event.getPassword()) && userinfo.getId().equals(event.getUser_id())) {
                // 해당 일자가 유효한지 검사 (Ex : 2024-02-30(false))
                if (formatCheck(String.valueOf(requestDto.getStartday())) && formatCheck(String.valueOf(requestDto.getEndday()))) {
                    // 일정종료일이 시작일 보다 빠를경우 (Ex : 2024-09-30 ~ 2024-08-30)
                    if (requestDto.getStartday().before(requestDto.getEndday())) {
                        eventRepository.update(id, requestDto);
                        return id;
                    } else {
                        throw new CustomException(END_DATE_BEFORE_START_DATE);
                    }
                } else {
                    throw new CustomException(DATE_PARSE_ERROR);
                }
            } else {
                throw new CustomException(USER_INFO_MISMATCH);
            }
        } else {
            throw new CustomException(OUT_OF_RANGE);
        }
    }
  • 레파지토리
    // 일정 ID 값을 기준으로 DB 데이터 수정 메소드
    public void update(Long id, EventRequestDto requestDto) {
        java.sql.Timestamp modifieddate = Timestamp.valueOf(LocalDateTime.now());
        String sql = "UPDATE event SET todo=?, modifieddate=?, startday =?, endday=?  WHERE id =?";
        jdbcTemplate.update(sql, requestDto.getTodo(), modifieddate, requestDto.getStartday(), requestDto.getEndday(), id);
    }
  • 요구 조건 :
    • 일정의 고유 식별자(ID)를 사용하여 정보를 수정합니다.
    • 수정 가능 컬럼은 할일 , 작성자명 만 수정 가능
    • 서버에 수정 요청시 비밀번호를 함께 전달
    • 작성일은 변경이 불가, 수정일은 수정 완료시 시점으로 변경.
  • 서비스 클래스에서 현재 선택한 ID의 Row 객체와 requestDto 의 비밀번호를 비교, 동일할시 수정, 불가능할시 예외발생.
  • 날짜관련 예외발생구문은 생성과 동일.
  • 정상수정

  • 비밀번호 오류

2. 일정 삭제

  • 컨트롤러
    // ID 값을 기준으로 정보를 삭제하는 컨트롤러 메소드
    // DTO 를 Body 로 받는 이유는 URL 이 아닌 Body 로 받으면서 보안성 향상.
    @DeleteMapping("/schedul/")
    public Long deleteEvent(@RequestParam Long id, @RequestBody EventRequestDto requestDto) {
        return eventService.deleteEvent(id, requestDto);
    }
  • 서비스 클래스
    // ID 기준의 DB 삭제 메소드
    public Long deleteEvent(Long id, EventRequestDto requestDto) {
        Event event = eventRepository.findId(id);
        // 해당 ID가 존재하면서 게시글 비밀번호와 동일한 시 정보 삭제
        if (event != null) if (requestDto.getPassword().equals(event.getPassword())) {
            eventRepository.delete(id);
            return id;
        } else {
            throw new CustomException(PASSWORD_EXCEPTION);
        }
        else {
            throw new CustomException(OUT_OF_RANGE);
        }
    }
  • 레파지토리
    // ID 값을 기준으로 DB 데이터 삭제 메소드
    public void delete(Long id) {
        String sql = "DELETE FROM event WHERE id=?";
        jdbcTemplate.update(sql, id);
    }
  • 요구 조건 :

    • 일정의 고유 식별자(ID)를 사용하여 정보를 삭제합니다.
    • 서버에 수정 요청시 비밀번호를 함께 전달
  • 서비스 클래스에서 현재 선택한 ID의 Row 객체와 requestDto 의 비밀번호를 비교, 동일할시 삭제, 불가능할시 예외발생.

  • 예외 발생구문은 수정작업과 동일.

  • 정상삭제

Lv.4

필수기능

사용기술

  • MySQL
  • DataBase
  • Spring Boot
  • Java
  • JDBC

1. DB 수정

  • Event Table 로만 정보를 가지고 이용하는게 아닌, User의 고유값과 정보를 가지고 있는 Table이 필요.
create table if not exists creator (
  id int PRIMARY KEY AUTO_INCREMENT,
  username varchar(5) not null ,
  email varchar(30) not null ,
  join_date DATETIME not null ,
  update_date datetime not null
);
  • id : User 들의 중복 데이터를 구분하기 위한 고유값 AUTO_INCREMENT 속성으로 자동 상승한다.
  • username : 기존의 event Table 이 가지던 유저 이름.
  • email : 유저가 가지는 e-mail 값 정규식으로 인해 이메일 패턴값만 받는다.
  • join_date : 유저가 등록된 일자.
  • update_date : 유저 정보 가 수정된 일자.
ALTER TABLE `calendar`.`event`
    CHANGE COLUMN `creator` `user_id` INT NOT NULL ;
    
ALTER TABLE event ADD username VARCHAR(5) NOT NULL;

ALTER TABLE event
ADD FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
  • 기존의 사용하던 Event Table 또한 User Table 의 데이터를 받아올수있게 수정하고 User 의 id 를 외래키 로 받아온다.
  • User의 정보가 삭제가 될 경우가 있으므로 ON DELETE CASCADE 를 통해 삭제된 User의 ID와 동일할 경우 일정을 전부 삭제한다.
  • username의 변경의 경우 외래키로 지정해서 처리할 수 있지만 다량의 외래키가 생길경우 좋지 않기때문에 Uesrname 이 변경처리 됄 경우 같이 변경 될수 있게 SQL을 지정해줬다.
    // 단일 User Data 를 DB 상에서 수정하는 메소드
    public void update(Long id, String username) {
        // 수정시간 입력을 위한 Timestamp 생성
        Timestamp update_date = Timestamp.valueOf(LocalDateTime.now());
        // 해당 ID를 가진 UserData 수정
        String sql = "UPDATE user SET username = ?, update_date = ? WHERE id = ?";
        jdbcTemplate.update(sql, username, update_date, id);
        // event Table 에 일정을 가지고 있을 경우 변경값으로 함께 변경
        sql = "UPDATE event SET username = ? WHERE user_id = ?";
        jdbcTemplate.update(sql, username, id);
    }

  • 생성

  • 변경(username)

  • 삭제

참조 :
https://daily-life-of-bsh.tistory.com/207
https://velog.io/@eensungkim/ON-DELETE-CASCADE-feat.-row-%ED%95%9C-%EB%B2%88%EC%97%90-%EC%A7%80%EC%9A%B0%EB%8A%94-%EB%B0%A9%EB%B2%95-TIL-78%EC%9D%BC%EC%B0%A8

Lv.5

필수기능

사용기술

  • MySQL
  • DataBase
  • Spring Boot
  • Java
  • JDBC

1. 페이징 기능 추가

  • 컨트롤러
    @GetMapping("/schedul/page/")
    public ResponseEntity getAllEventsPage(@Positive @RequestParam int page, @Positive @RequestParam int size) {
        Page<Event> eventsPage = eventService.findPageEvents(page - 1, size);
        PageInfo pageInfo = new PageInfo(page, size, (int) eventsPage.getTotalElements(), eventsPage.getTotalPages());
        List<Event> events = eventsPage.getContent();
        List<EventResponseDto> respones = eventMapper.eventsToEventResponseDtos(events);
        return new ResponseEntity<>(new PageResponseDto(respones, pageInfo), HttpStatus.OK);
    }
  • 서비스
    public Page<Event> findPageEvents(int page, int size) {
        PageRequest pageRequest = PageRequest.of(page, size);
        return eventPageRepository.findAllByOrderByIdDesc(pageRequest);
    }
  • 레파지토리
public interface EventPageRepository extends CrudRepository<Event, Long> {
    Page<Event> findAllByOrderByIdDesc(Pageable pageable);
}
  • Mapper
@Mapper(componentModel = "spring")
public interface EventMapper {
    // Mapper 를 DI 받아 Entity 와 Dto 를 맵핑 해주는 역할
    List<EventResponseDto> eventsToEventResponseDtos(List<Event> events);
}
  • DTO
@Getter
@Setter
@AllArgsConstructor
// 전체 일정을 담고있는 Data 와 page Info 를 가지는 DTO
public class PageResponseDto {
    private List<EventResponseDto> data;
    private PageInfo pageInfo;
}
@Getter
@Setter
@AllArgsConstructor
// Page 의 정보를 가지고 있는 Info 객체
public class PageInfo {
    private int page;
    private int size;
    private int totalElements;
    private int totalPages;
}
  • 요구 조건 :
    • 모든 일정을 페이지번호,크기를 기준으로 모두 조회.
    • 조회한 일정목록에 작성자 이름 포함
    • 범위를 넘어갈시 빈 배열 포함
  • 페이징 구현 방식은 Offset 구현방식으로 구현.
  • Pagination을 적용하기 위해 Pageable 인터페이스를 활용
  • 컨트롤러에서 원하는 페이지 및 슬라이스 사이즈를 요청 파라미터로 전달.
  • PageRequest 클래스는 Pageable 인터페이스를 구현한 추상화 객체를 상속받은 클래스로 이를 통해 객체를 반환 받는다.
  • CrudRepository를 상속받아 Repository를 구현 했으므로 메소드 명 을 적절히 작성하여 필요로 하는 데이터를 DB로 요청이 필요.
    • findAllByOrderByIdDesc 는 Event의 id 값 을 기준으로 정보를 가져온다는 뜻.
  • PageResponseDto 객체를 통하여 JSON 타입으로 반환해준다.
  • DB 구조
  • page : 1, Size : 5

  • page : 3, Size : 5 (빈배열 반환)

참조 :
https://velog.io/@sussa3007/Spring-Spring-DATA-JDBC-Pagination-API#-repository-%EA%B5%AC%ED%98%84
https://mason-lee.tistory.com/108
https://clgitory.tistory.com/m/40

트러블 슈팅

링크(클릭)

구현기능

API 명세서 링크(클릭)

프로그램 주요기능 플로우차트

  • 일정 생성
  • 일정 조회
  • 일정 수정
  • 일정 삭제

프로그램 동작화면

  • 일정 생성
  • 일정 조회 (페이징)
  • 일정 수정
  • 일정 삭제

Stack

Architecture

📦 
├─ .gitignore
├─ build.gradle
├─ gradle
│  └─ wrapper
│     ├─ gradle-wrapper.jar
│     └─ gradle-wrapper.properties
├─ gradlew
├─ gradlew.bat
├─ settings.gradle
└─ src
   ├─ main
   │  ├─ java
   │  │  └─ com
   │  │     └─ sparta
   │  │        └─ calendarprojects
   │  │           ├─ CalendarProjectsApplication.java
   │  │           ├─ controller : 유저, 일정 관련 API 컨트롤러 패키지
   │  │           │  ├─ EventController.java : 일정 관리 컨트롤러
   │  │           │  └─ UserController.java : 유저 관리 컨트롤러
   │  │           ├─ dto : 유저, 일정 관련 정보처리 DTO 패키지
   │  │           │  ├─ EventRequestDto.java : 일정관련 요청정보 DTO
   │  │           │  ├─ EventResponseDto.java : 일정관련 응답정보 DTO
   │  │           │  ├─ PageResponseDto.java : 일정 페이징 정보 DTO
   │  │           │  ├─ UserReponseDto.java : 유저관련 응답정보 DTO
   │  │           │  └─ UserRequestDto.java : 유저관련 요청정보 DTO
   │  │           ├─ entity : DB 조회용 Entity 패키지
   │  │           │  ├─ Event.java : 일정 관련 Entity
   │  │           │  └─ User.java : 유저 관련 Entity
   │  │           ├─ exception : 예외처리 패키지
   │  │           │  ├─ CustomErrorCode.java : 예외발생 에러 코드 ENUM 클래스
   │  │           │  ├─ CustomException.java : 예외 객체 생성용 상속 클래스
   │  │           │  ├─ CustomExceptionHandler.java : 예외 핸들링 클래스
   │  │           │  └─ ErrorResponse.java : 예외정보 DTO
   │  │           ├─ info : 서버안에서 운용하는 Info 객체
   │  │           │  └─ PageInfo.java : 페이징 정보 객체
   │  │           ├─ mapper : 페이징 기능 활용 mapper 패키지
   │  │           │  └─ EventMapper.java : Page 기능을 활용해 DB에 객체들을 맵핑해주는 Mapper
   │  │           ├─ repository : 유저, 일정 관련 DB 조회 레파지토리 패키지
   │  │           │  ├─ EventPageRepository.java : 페이징 기능 활용을 위한 인터페이스 구현체 레파지토리
   │  │           │  ├─ EventRepository.java : 일정 관련 DB 관리 레파지토리
   │  │           │  └─ UserRepository.java : 유저 관련 DB 관리 레파지토리
   │  │           └─ service : 유저, 일정 관련 서비스 로직 패키지
   │  │              ├─ EventService.java : 일정 관련 서비스로직
   │  │              └─ UserService.java : 유저 관련 서비스로직
   │  └─ resources
   │     └─ application.properties
   └─ test
      └─ java
         └─ com
            └─ sparta
               └─ calendarprojects
                  └─ CalendarProjectsApplicationTests.java

©generated by Project Tree Generator

Github 링크

링크(클릭)

profile
70살까지 개발하고싶은 개발자

0개의 댓글