
문제발생 : 일정을 생성하며 날짜 형태 컬럼들인 생성일, 수정일 및 일정 시작날짜, 종료날짜를 DateTime, Date 형식으로 지정하면서 기존의 String으로 지정되있던 DATA 저장형 객체들의 오류가발생.
가설 : APP을 작성하면서 DB가 매치가 안되는 데이터타입이 존재할리없음. JAVA에서 따로 시간형에 대한 지원라이브러리가 존재하지않을까?
문제해결 : 해당 문제유형에는 내가 아는 2가지 문제해결방식이 존재함 (물론 수많은 해결책이 존재할것이다.)
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class EventResponseDto {
private Long id;
private String creator;
private String todo;
private String password;
private java.sql.Timestamp createddate;
private java.sql.Timestamp modifieddate;
private java.sql.Date startday;
private java.sql.Date endday;
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");
참조 :
https://m.blog.naver.com/heeju_99/222217060845
https://adjh54.tistory.com/500
// 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);
}
});
}
문제발생 : DB에 존재하는 일정들의 검색조건은 작성자명/수정일 의 검색입력에 대한 값을 가짐. 조건 중 한 가지만을 충족하거나, 둘 다 충족을 하지 않을 수도, 두 가지를 모두 충족할 수 있음. 즉 WHERE 절의 변화가 일어남.
원인 : 해당 문제에 대해서 jdbcTemplate.query(sql, new RowMapper<EventResponseDto>() { ... }) 메소드내에서 SQL 값이 유동적일경우 ? 식 와일드카드가 들어가 파라미터값이 항상 변해야하는 구조가 발생.
EX)
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); // 해당 부분
}
가설 : if문을 통해 SQL 문도 결국 String 문자열이기에 조절이 가능하지않을까?
문제해결 : String을 조절하는식으로 SQL문을 조절하여 해결. String 불변성에 의해 성능이 나빠지면 StringBuffer 혹은 Heap 내에서 사용되는 데이터를 캐싱해서 사용하면 성능개선이 충분할거라 판단됌.
생각해볼점 : 결국 String 값으로 SQL문을 사용하기에 SQL Injection 에 취약한 형태가 아닐까 생각됌.
참조 : 6조 팀원들.
SQL Injection 이란?


문제발생 : Exception 객체 throw 통해 예외를 발생시키고는 있지만 기존의 예외처리처럼 서버 Log로만 발생하는게 아닌 Client 에게 명시적으로 해당예외를 구체적으로 전송이 있으면 좋겠다고 생각. (이후 @Valid 를 추가, Service 단 에서의 값 검증을 최소화 했다.)
(물론 HTTP 내부적으로 예외 JSON을 전달은 하지만 조금더 구체적으로 봤을때)
원인 : 예외발생시 따로 Respones 객체를 생성, 전달하는게 아닌 에러 로그로만 발생 시키고 있음.
가설 : 서버가 정보를 DTO화해서 객체로 보내는거 처럼 예외 객체도 전송이 가능한것 아닐까?
문제해결 : Spring의 커스텀 예외들을 찾아보며 기존의 자바 예외와 크게 달라진것 없이 형태만 바뀌었다고 느낌.

@NoArgsConstructor
@Getter
public enum CustomErrorCode {
// 예외 처리를 위한 eunm 객체 속성 생성.
USER_INFO_MISMATCH("유저정보가 일치하지않습니다."),
END_DATE_BEFORE_START_DATE("일정 종료일자가 시작일자보다 빠릅니다."),
NULL_BLANK_INPUT("입력을 하지 않은 값이 존재합니다. 모든값을 입력하세요."),
OUT_OF_RANGE("선택한 ID 값이 존재하지 않습니다."),
DATE_PARSE_ERROR("날짜 형태가 맞지않거나 해당날짜가 유효하지 않습니다. Ex) 2024-02-30 , YYYY-MM-DD)"),
INVALID_EMAIL_FORMAT("이메일 형태가 올바르지 않습니다."),
USER_NOT_FOUND("해당 유저가 존재하지 않습니다."),
INPUT_OUT_OF_BOUNDS("입력 조건이 최소/최대값 과 맞지않습니다.");
private String statusMessage;
// 예외 발생시 Message 출력을 위한 생성자
CustomErrorCode(String statusMessage) {
this.statusMessage = statusMessage;
}
}
@Getter
@Setter
@AllArgsConstructor
@Builder
public class ErrorResponse {
private CustomErrorCode code;
private String description;
private String detail;
public ErrorResponse(CustomErrorCode code, String description) {
this.code = code;
this.description = description;
}
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
// 사용자예외를 발생시키기 위한 예외 클래스
public class CustomException extends RuntimeException {
private CustomErrorCode customErrorCode;
private String detailMessage;
public CustomException(CustomErrorCode customErrorCode) {
super(customErrorCode.getStatusMessage());
this.customErrorCode = customErrorCode;
this.detailMessage = customErrorCode.getStatusMessage();
}
}
@Slf4j
@RestControllerAdvice
// 사용자 예외 발생시 예외를 처리해주는 Handler Class
public class CustomExceptionHandler {
@ExceptionHandler(CustomException.class)
// 예외 발생시 예외에 대한 DTO 객체를 생성해준다
public ErrorResponse handleCustomException(CustomException e, HttpServletRequest request) {
log.error("errorCode : {}, url : {}, message: {}", e.getCustomErrorCode(), request.getRequestURL(), e.getDetailMessage());
return new ErrorResponse(e.getCustomErrorCode(), e.getDetailMessage(), e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> methodValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
log.warn("MethodArgumentNotValidException 발생!!! url:{}, trace:{}", request.getRequestURI(), e.getStackTrace());
ErrorResponse errorResponse = makeErrorResponse(e.getBindingResult());
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.BAD_REQUEST);
}
private ErrorResponse makeErrorResponse(BindingResult bindingResult) {
CustomErrorCode code = null;
String description = "";
String detail = "";
if (bindingResult.hasErrors()) {
//DTO에 설정한 meaasge값을 가져온다
detail = bindingResult.getFieldError().getDefaultMessage();
//DTO에 유효성체크를 걸어놓은 어노테이션명을 가져온다.
String bindResultCode = bindingResult.getFieldError().getCode();
switch (bindResultCode) {
case "NotBlank":
code = CustomErrorCode.NULL_BLANK_INPUT;
description = CustomErrorCode.NULL_BLANK_INPUT.getStatusMessage();
break;
case "NotNull":
code = CustomErrorCode.NULL_BLANK_INPUT;
description = CustomErrorCode.NULL_BLANK_INPUT.getStatusMessage();
break;
case "Email":
code = CustomErrorCode.INVALID_EMAIL_FORMAT;
description = CustomErrorCode.INVALID_EMAIL_FORMAT.getStatusMessage();
break;
case "Size":
code = CustomErrorCode.INPUT_OUT_OF_BOUNDS;
description = CustomErrorCode.INPUT_OUT_OF_BOUNDS.getStatusMessage();
break;
}
}
return new ErrorResponse(code, description, detail);
}
}
CustomErrorCode 에서 예외상태를 정의해준 후 ExceptionHandler 를 통해 해당예외가 발생시 메소드로 유도 및 예외정보를 가지고 ErrorResponse 객체를 만들어 Client 에게 전달한다.
참조 :
https://velog.io/@hwsa1004/Spring-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%ACCustom-Exception-%EB%A7%8C%EB%93%A4%EA%B8%B0
https://tecoble.techcourse.co.kr/post/2020-08-17-custom-exception/
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Valid
public class EventRequestDto {
@Positive
private Long id;
@NotNull(message = "유저 ID를 입력해주세요.")
@Positive
private Long user_id;
@NotBlank(message = "일정을 입력해주세요.")
@Size(min = 1, max = 500, message = "일정은 최소 1글자 최대 500글자 입력가능")
private String todo;
@NotBlank(message = "비밀번호를 입력해주세요.")
private String password;
private java.sql.Timestamp createddate;
private java.sql.Timestamp modifieddate;
@NotNull(message = "일정시작일을 입력해주세요.")
private java.sql.Date startday;
@NotNull(message = "일정종료일을 입력해주세요.")
private java.sql.Date endday;
}
// 일정을 생성하는 컨트롤러 메소드
@PostMapping("/schedul/")
public EventResponseDto createEvent(@RequestBody @Valid EventRequestDto requestDto) {
return eventService.createEvent(requestDto);
}
@NotNull(message = "일정시작일을 입력해주세요.") 등 유효조건에 맞는지 검사하고 해당값이 유효가 아니면 예외발생 및 처리방식을 사용했다.

FK 제약으로 인해 삭제가 불가능 한 문제가 생김.FK 의 경우 다른 테이블에서도 데이터가 일관되고 정합성이 지켜지지만 그로 인해 다른테이블의 묶여있는 컬럼의 수정이 기본적으론 막혀있음.ON DELETE CASCADE 의 속성을 통해 제약조건에게 해당컬럼의 변경사항을 따라가는 조건을 추가해서 해결.ALTER TABLE event
ADD FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
FK 가 많아질 경우 장점도 있지만 단점이 더 크다 판단. 외래키를 사용하지않고 업데이트를 시행. // 단일 User Data 를 DB 상에서 수정하는 메소드
public void update(Long id, UserRequestDto requestDto) {
// 수정시간 입력을 위한 Timestamp 생성
Timestamp update_date = Timestamp.valueOf(LocalDateTime.now());
// 해당 ID를 가진 UserData 수정
String sql = "UPDATE user SET username = ?, update_date = ? WHERE id = ?";
jdbcTemplate.update(sql, requestDto.getUsername(), update_date, id);
// event Table 에 일정을 가지고 있을 경우 변경값으로 함께 변경
sql = "UPDATE event SET username = ? WHERE user_id = ?";
jdbcTemplate.update(sql, requestDto.getUsername(), id);


Mapper 란?
- MapStruct는 configuration 접근 방식의 규칙을 기반으로 한 Java 빈 타입의 매핑 구현을 단순화해주는 코드 생성기.
- Later 간 이동을 위해 다양한 DTO가 발생. 필드간 차이가 발생시 MapStruct를 통해 객체 내 많은 필드에 대해서 쉽게 매핑
@Mapper 을 사용시 인식하지 못하거나 서버가 실행되지않는 상황이 발생 (Mapper를 Bean으로 생성하지 못함).implementation "org.mapstruct:mapstruct:1.5.2.Final"
annotationProcessor "org.mapstruct:mapstruct-processor:1.5.2.Final"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:1.5.2.Final"
implementation 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
참조 : https://mein-figur.tistory.com/entry/mapstruct-1


문제발생 : 페이징 문제를 해결하던 도중 지속적으로 Repository 의 SQL의 문제가 있어서 Bean 객체로 등록이 불가능해 서버를 실행할수없다는 오류문 출력
원인 : CrudRepository 를 상속받아 메소드를 작성한 경우 해당 메소드명 을 통해 DB단으로 진입해 Entity 를 작성하게 되어있음.

가설 : 메소드명이 MemberID 로 고정되어 있지않고 해당 메서드명을 파생하여서 SQL문이 작성되어지지 않을까? 생각.
문제해결 : 해당 메서드명을 Table명 포함, 컬럼명 등 계속 비교해가며 작성 프로그램에 적절하게 교체하여 문제 해결. (솔직히 메소드명을 통해서 SQL 이 파생된다고는 생각못했다...)
(Spring의 특징으로 생략되어진 기능이 상당히 많은거 같다느낌.)
//Entity 를 Id 기준 내림차순으로 데이터를 받기위한 CrudRepository 요청 메소드
public interface EventPageRepository extends CrudRepository<Event, Long> {
Page<Event> findAllByOrderByIdDesc(Pageable pageable);
}
참조 :
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