내배캠 TIL 26일차

오병택·2025년 3월 25일

내배캠

목록 보기
48/73

학습 요약

Schedule

Schedule lv1

crud 기능에 대한 과제입니다. 일단 패키지는 controller,dto,entity,repository, service로 만들었습니다.

ScheduleController클래스

package com.example.schedule.controller;

import com.example.schedule.dto.ScheduleRequestDto;
import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.service.ScheduleService;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/schedules")
public class ScheduleController {
    private final ScheduleService scheduleService;


    public ScheduleController(ScheduleService scheduleService) {
        this.scheduleService = scheduleService;
    }

    // 일정 등록
    @PostMapping
    public ResponseEntity<ScheduleResponseDto> createSchedule(@RequestBody ScheduleRequestDto dto) {
        return new ResponseEntity<>(scheduleService.saveSchedule(dto), HttpStatus.CREATED);
    }



    // 전체 일정 조회
    @GetMapping
    public List<ScheduleResponseDto> findAllSchedules(){

        return scheduleService.findAllSchedules();
    }

    // 선택 일정 조회
    @GetMapping("/{id}")
    public ResponseEntity<ScheduleResponseDto>findScheduleById(@PathVariable Long id){

        return new ResponseEntity<>(scheduleService.findScheduleById(id),HttpStatus.OK);
    }

    // 선택 일정 수정
//    @PostMapping("/{id}")

    // 선택 일정 삭제
//    @DeleteMapping("/{id}")
}

ScheduleService인터페이스

package com.example.schedule.service;

import com.example.schedule.dto.ScheduleRequestDto;
import com.example.schedule.dto.ScheduleResponseDto;

import java.util.List;

public interface ScheduleService {
    ScheduleResponseDto saveSchedule(ScheduleRequestDto dto);
    List<ScheduleResponseDto> findAllSchedules();
    ScheduleResponseDto findScheduleById(Long id);
}

ScheduleServiceImpl클래스

package com.example.schedule.service;

import com.example.schedule.dto.ScheduleRequestDto;
import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.entity.Schedule;
import com.example.schedule.repository.ScheduleRepository;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class ScheduleServiceImpl implements ScheduleService{
    private final ScheduleRepository scheduleRepository;

    public ScheduleServiceImpl(ScheduleRepository scheduleRepository) {
        this.scheduleRepository = scheduleRepository;
    }
    
	// service) 일정 저장 메서드
    @Override
    public ScheduleResponseDto saveSchedule(ScheduleRequestDto dto) {
        Schedule schedule = new Schedule(dto.getName(),dto.getPassword(),dto.getTodo(), LocalDateTime.now(),LocalDateTime.now());

        return scheduleRepository.saveSchedule(schedule);
    }
    
	// service) 일정 목록 조회 메서드
    @Override
    public List<ScheduleResponseDto> findAllSchedules() {

        return scheduleRepository.findAllSchedules();
    }
    
	// service) 일정 단건 조회 메서드
    @Override
    public ScheduleResponseDto findScheduleById(Long id) {

        return new ScheduleResponseDto(scheduleRepository.findScheduleByIdOrElseThrow(id));
    }
}

ScheduleRepository인터페이스

package com.example.schedule.repository;

import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.entity.Schedule;
import java.util.List;

public interface ScheduleRepository {
    ScheduleResponseDto saveSchedule(Schedule schedule);
    List<ScheduleResponseDto> findAllSchedules();
    Schedule findScheduleByIdOrElseThrow(Long id);
}

ScheduleRepositoryImpl클래스

package com.example.schedule.repository;

import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.entity.Schedule;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.web.server.ResponseStatusException;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class ScheduleRepositoryImpl implements ScheduleRepository{

    private final JdbcTemplate jdbcTemplate;

    public ScheduleRepositoryImpl(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    
	// repository) 일정 저장 메서드
    @Override
    public ScheduleResponseDto saveSchedule(Schedule schedule) {

        SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        simpleJdbcInsert.withTableName("schedule").usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", schedule.getName());
        parameters.put("password", schedule.getPassword());
        parameters.put("todo", schedule.getTodo());
        parameters.put("creationdate", schedule.getCreationdate());
        parameters.put("modificationdate", schedule.getModificationdate());

        Number key = simpleJdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        return new ScheduleResponseDto(key.longValue(), schedule.getName(),schedule.getTodo(),
                schedule.getCreationdate(),schedule.getModificationdate());
    }
    
	// repository) 일정 목록 조회 메서드
    @Override
    public List<ScheduleResponseDto> findAllSchedules() {
        return jdbcTemplate.query("select * from schedule order by modificationdate desc", scheduleRowMapper());
    }
    
	// repository) 일정 단건 조회 메서드
    @Override
    public Schedule findScheduleByIdOrElseThrow(Long id) {
        List<Schedule> result = jdbcTemplate.query("select * from schedule where id = ?", scheduleRowMapperv2(), id);
        return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exists id = "+id));
    }
	
    // 각 행을 ScheduleResponseDto 타입으로 매핑 후 리스트로 반환해주는 메서드
    private RowMapper<ScheduleResponseDto> scheduleRowMapper(){
        return new RowMapper<ScheduleResponseDto>() {
            @Override
            public ScheduleResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new ScheduleResponseDto(rs.getLong("id"), rs.getString("name"),
                        rs.getString("todo"),
                        rs.getTimestamp("creationdate").toLocalDateTime(),
                        rs.getTimestamp("modificationdate").toLocalDateTime());
            }
        };
    }
    
    // 각 행을 Schedule 타입으로 매핑 후 리스트로 반환해주는 메서드
    private RowMapper<Schedule> scheduleRowMapperv2() {
        return new RowMapper<Schedule>() {
            @Override
            public Schedule mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new Schedule(rs.getLong("id"),rs.getString("name"),rs.getString("password"),
                        rs.getString("todo"),rs.getTimestamp("creationdate").toLocalDateTime(),
                        rs.getTimestamp("modificationdate").toLocalDateTime());
            }
        };
    }
}

ScheduleRequestDto클래스

package com.example.schedule.dto;


import lombok.Getter;

@Getter
public class ScheduleRequestDto {
    private String name;
    private String password;
    private String todo;

}

ScheduleResponseDto클래스

package com.example.schedule.dto;

import com.example.schedule.entity.Schedule;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDateTime;

@AllArgsConstructor
@Getter
public class ScheduleResponseDto {
    private Long id;
    private String name;
    private String todo;
    private LocalDateTime creationdate;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDateTime modificationdate;

    public ScheduleResponseDto(Schedule schedule) {
        this.id=schedule.getId();
        this.name=schedule.getName();
        this.todo=schedule.getTodo();
        this.creationdate=schedule.getCreationdate();
        this.modificationdate=schedule.getModificationdate();
    }
}

Schedule클래스

package com.example.schedule.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;

@AllArgsConstructor
@Getter
public class Schedule {
    private Long id;
    private String name;
    private String password;
    private String todo;
    private LocalDateTime creationdate;
    private LocalDateTime modificationdate;

    public Schedule(String name, String password, String todo,
                    LocalDateTime creationdate,LocalDateTime modificationdate) {
        this.name= name;
        this.password = password;
        this.todo = todo;
        this.creationdate=creationdate;
        this.modificationdate=modificationdate;
    }
}

Schedule lv2

최대한 바뀐 부분만 올리려고 노력해보겠습니다..

ScheduleController클래스

	// 선택 일정 조회
    @GetMapping("/{id}")
    public ResponseEntity<ScheduleResponseDto>findScheduleById(@PathVariable Long id){

        return new ResponseEntity<>(scheduleService.findScheduleById(id),HttpStatus.OK);
    }
    // 선택 일정 수정
    @PutMapping("/{id}")
    public ResponseEntity<ScheduleResponseDto> updateSchedule(@PathVariable Long id, @RequestBody ScheduleRequestDto dto ){
        return new ResponseEntity<>(scheduleService.updateSchedule(id,dto.getPassword(), dto.getName(),dto.getTodo()),HttpStatus.OK);
    }

    // 선택 일정 삭제
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteSchedule(@PathVariable Long id, @RequestBody ScheduleRequestDto dto){
        scheduleService.deleteSchedule(id,dto.getPassword());
        return new ResponseEntity<>(HttpStatus.OK);
    }

ScheduleService인터페이스

package com.example.schedule.service;

import com.example.schedule.dto.ScheduleRequestDto;
import com.example.schedule.dto.ScheduleResponseDto;

import java.util.List;

public interface ScheduleService {
    ScheduleResponseDto saveSchedule(ScheduleRequestDto dto);
    List<ScheduleResponseDto> findAllSchedules();
    ScheduleResponseDto findScheduleById(Long id);
    ScheduleResponseDto updateSchedule(Long id,String password, String name, String todo);
    void deleteSchedule(Long id,String password);
}

ScheduleServiceImpl클래스

package com.example.schedule.service;

import com.example.schedule.dto.ScheduleRequestDto;
import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.entity.Schedule;
import com.example.schedule.repository.ScheduleRepository;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.time.LocalDateTime;
import java.util.List;

@Service
public class ScheduleServiceImpl implements ScheduleService{
    private final ScheduleRepository scheduleRepository;

    public ScheduleServiceImpl(ScheduleRepository scheduleRepository) {
        this.scheduleRepository = scheduleRepository;
    }
    
	// service) 일정 등록 메서드
    @Override
    public ScheduleResponseDto saveSchedule(ScheduleRequestDto dto) {
        Schedule schedule = new Schedule(dto.getName(),dto.getPassword(),dto.getTodo(), LocalDateTime.now(),LocalDateTime.now());

        return scheduleRepository.saveSchedule(schedule);
    }
	
    // service) 일정 목록 조회 메서드
    @Override
    public List<ScheduleResponseDto> findAllSchedules() {

        return scheduleRepository.findAllSchedules();
    }
	
    // service) 일정 단건 조회 메서드
    @Override
    public ScheduleResponseDto findScheduleById(Long id) {

        return new ScheduleResponseDto(scheduleRepository.findScheduleByIdOrElseThrow(id));
    }
	
    // service) 일정 수정 메서드
    @Transactional
    @Override
    public ScheduleResponseDto updateSchedule(Long id, String password, String name, String todo) {

        if (name==null || todo==null || password==null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The name and todo and password is required values.");
        }

        if (password.equals(scheduleRepository.findScheduleByIdOrElseThrow(id).getPassword())) {
            int updatedRow = scheduleRepository.updateSchedule(id, password,name, todo);

            if (updatedRow==0) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND,"Does not exist id="+id);
            }

            Schedule schedule = scheduleRepository.findScheduleByIdOrElseThrow(id);

            return new ScheduleResponseDto(schedule);
        }
        throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "password is different");
    }
    
	// service) 일정 삭제 메서드
    @Override
    public void deleteSchedule(Long id,String password) {
        if (password==null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The password is required values.");
        }

        if (password.equals(scheduleRepository.findScheduleByIdOrElseThrow(id).getPassword())) {
            int deletedRow = scheduleRepository.deleteSchedule(id,password);

            if(deletedRow==0) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND,"Does not exist id="+id);
            }throw new ResponseStatusException(HttpStatus.OK);
        }
        throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "password is different");
    }
}

ScheduleRepository인터페이스

package com.example.schedule.repository;

import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.entity.Schedule;
import java.util.List;

public interface ScheduleRepository {
    ScheduleResponseDto saveSchedule(Schedule schedule);
    List<ScheduleResponseDto> findAllSchedules();
    Schedule findScheduleByIdOrElseThrow(Long id);
    int updateSchedule(Long id,String password, String name,String todo);
    int deleteSchedule(Long id, String password);
}

ScheduleRepositoryImpl클래스

package com.example.schedule.repository;

import com.example.schedule.dto.ScheduleResponseDto;
import com.example.schedule.entity.Schedule;
import org.springframework.http.HttpStatus;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import org.springframework.web.server.ResponseStatusException;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class ScheduleRepositoryImpl implements ScheduleRepository{

    private final JdbcTemplate jdbcTemplate;

    public ScheduleRepositoryImpl(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    
    // repository) 일정 등록 메서드
    @Override
    public ScheduleResponseDto saveSchedule(Schedule schedule) {

        SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        simpleJdbcInsert.withTableName("schedule").usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", schedule.getName());
        parameters.put("password", schedule.getPassword());
        parameters.put("todo", schedule.getTodo());
        parameters.put("creationdate", schedule.getCreationdate());
        parameters.put("modificationdate", schedule.getModificationdate());

        Number key = simpleJdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        return new ScheduleResponseDto(key.longValue(), schedule.getName(),schedule.getTodo(),
                schedule.getCreationdate(),schedule.getModificationdate());
    }
    
	// repository) 일정 목록 조회 메서드
    @Override
    public List<ScheduleResponseDto> findAllSchedules() {
        return jdbcTemplate.query("select * from schedule order by modificationdate desc", scheduleRowMapper());
    }
    
	// repository) 일정 단건 조회 메서드
    @Override
    public Schedule findScheduleByIdOrElseThrow(Long id) {
        List<Schedule> result = jdbcTemplate.query("select * from schedule where id = ?", scheduleRowMapperv2(), id);
        return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exists id = "+id));
    }
    
	// repository) 일정 수정 메서드
    @Override
    public int updateSchedule(Long id, String password, String name, String todo) {
        return jdbcTemplate.update("update schedule set name=?, todo=?, modificationdate=? where id=?",name,todo, LocalDateTime.now(),id);
    }
    
	// repository) 일정 삭제 메서드
    @Override
    public int deleteSchedule(Long id, String password) {
        return jdbcTemplate.update("delete from schedule where id=?",id);
    }
    
	// 각 행을 ScheduleResponseDto 타입으로 매핑 후 리스트로 반환해주는 메서드
    private RowMapper<ScheduleResponseDto> scheduleRowMapper(){
        return new RowMapper<ScheduleResponseDto>() {
            @Override
            public ScheduleResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new ScheduleResponseDto(rs.getLong("id"), rs.getString("name"),
                        rs.getString("todo"),
                        rs.getTimestamp("creationdate").toLocalDateTime(),
                        rs.getTimestamp("modificationdate").toLocalDateTime());
            }
        };
    }
    
    // 각 행을 Schedule 타입으로 매핑 후 리스트로 반환해주는 메서드
    private RowMapper<Schedule> scheduleRowMapperv2() {
        return new RowMapper<Schedule>() {
            @Override
            public Schedule mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new Schedule(rs.getLong("id"),rs.getString("name"),rs.getString("password"),
                        rs.getString("todo"),rs.getTimestamp("creationdate").toLocalDateTime(),
                        rs.getTimestamp("modificationdate").toLocalDateTime());
            }
        };
    }
}

ScheduleResponseDto클래스

package com.example.schedule.dto;

import com.example.schedule.entity.Schedule;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.time.LocalDateTime;

@AllArgsConstructor
@Getter
public class ScheduleResponseDto {
    private Long id;
    private String name;
    private String todo;
    private LocalDateTime creationdate;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private LocalDateTime modificationdate;

    public ScheduleResponseDto(Schedule schedule) {
        this.id=schedule.getId();
        this.name=schedule.getName();
        this.todo=schedule.getTodo();
        this.creationdate=schedule.getCreationdate();
        this.modificationdate=schedule.getModificationdate();
    }
}

Schedule클래스

package com.example.schedule.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;

@AllArgsConstructor
@Getter
public class Schedule {
    private Long id;
    private String name;
    private String password;
    private String todo;
    private LocalDateTime creationdate;
    private LocalDateTime modificationdate;

    public Schedule(String name, String password, String todo,
                    LocalDateTime creationdate,LocalDateTime modificationdate) {
        this.name= name;
        this.password = password;
        this.todo = todo;
        this.creationdate=creationdate;
        this.modificationdate=modificationdate;
    }
}

음 너무 코드만 많아서 좀 더러워보이긴 한데.. 막상 잘 읽어보면 잘 읽히리라 믿습니다.🙄 내일 과제 제출할 땐 주석처리 좀 해서 내는 걸로..

트러블 슈팅

에러난 부분

lv1을 다 만들고 나서 일정 등록하는 부분을 포스트맨으로 테스트를 해봤는데 406
Not Acceptable 에러가 떴다.
에러 코드: Resolved[org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation]

원인

400번대 에러면 클라이언트 잘못인데 아무리 봐도 내가 뭔가 실수한 것 같지는 않아서 웹 서칭을 해보니 406 에러코드 설명은 서버가 요청의 사전 콘텐츠 협상 헤더에 정의된 허용 가능한 값 목록과 일치하는 응답을 생성 할 수 없으며 서버가 기본 표현을 제공하지 않음이라고 하고 @Getter를 안 붙이면 뜬다고 했다.

해결

@Getter를 붙이니 해결이 되어 잘 작동한다.

그 후

해결은 했지만 왜 Getter를 사용하는 곳이 하나도 없다고 뜨는데 Getter를 붙이는지 이해가 안 되는 것 같아서 튜터님을 찾아가서 물어보았다. Jackson 라이브러리에서 리스폰스 응답객체를 확인하고 json으로 바꿔주는데 그 확인하는 방법이 getter를 통해서 확인하기 때문에 getter를 안 붙이게 되면 json형태로 되돌려 줄 수 없어서 406에러가 발생하는 것이라고 알려주셨다. 다음부터는 이런 에러를 마주쳐도 해결할 수 있을 것 같다.

느낀 점

과제를 하루 종일 하다 보니 좀 뭔가 기계가 된 느낌이랄까 성취의 뿌듯함을 느낄 새도 없이 계속해서 만들어내는 느낌이라 조금 답답한 느낌이다. 환기를 좀 시켜야 될 것 같기도 한데 또 숙련 시작이라니..😭 그래도 코드를 짜니까 시간은 빠르게 흐르는 것 같다. 요즘 계속 TIL이 밀리고 내 맘 같지 않네..

profile
걱정하지 말고 일단 해봐!

0개의 댓글