Today's Codekata
class Solution {
public String solution(String X, String Y) {
int[] xCount = new int[10];
int[] yCount = new int[10];
for (char ch : X.toCharArray()) {
xCount[ch - '0']++;
}
for (char ch : Y.toCharArray()) {
yCount[ch - '0']++;
}
StringBuilder answer = new StringBuilder();
for (int i = 9; i >= 0; i--) {
int common = Math.min(xCount[i], yCount[i]);
for (int j = 0; j < common; j++) {
answer.append(i);
}
}
if (answer.length() == 0) return "-1";
if (answer.charAt(0) == '0') return "0";
return answer.toString();
}
}
중복 없이 공통 숫자를 찾는 건 어렵지 않았는데, 숫자가 여러 번 나올 때 처리하는 게 까다로웠다. 그래도 이 문제를 통해 문자열 처리, 배열 사용에 대해 더 이해할 수 있었고, `StringBuilder`를 사용하는 방법도 배울 수 있었다.
-- 오프라인/온라인 판매 데이터 통합하기
SELECT DATE_FORMAT(SALES_DATE, "%Y-%m-%d") AS SALES_DATE,
PRODUCT_ID, USER_ID, SALES_AMOUNT
FROM ONLINE_SALE
WHERE SALES_DATE BETWEEN '2022-03-01' AND '2022-03-31'
UNION ALL
SELECT
DATE_FORMAT(SALES_DATE, "%Y-%m-%d") AS SALES_DATE,
PRODUCT_ID, NULL AS USER_ID, SALES_AMOUNT
FROM OFFLINE_SALE
WHERE SALES_DATE BETWEEN '2022-03-01' AND '2022-03-31'
ORDER BY
SALES_DATE , PRODUCT_ID , USER_ID;
-- 조건에 부합하는 중고거래 댓글 조회하기
SELECT B.TITLE, B.BOARD_ID, R.REPLY_ID, R.WRITER_ID, R.CONTENTS,
DATE_FORMAT(R.CREATED_DATE, "%Y-%m-%d") AS CREATED_DATE
FROM USED_GOODS_BOARD B
JOIN USED_GOODS_REPLY R ON B.BOARD_ID = R.BOARD_ID
WHERE B.CREATED_DATE BETWEEN '2022-10-01' AND '2022-10-30'
ORDER BY R.CREATED_DATE, B.TITLE;
-- 입양 시각 구하기(2)
SELECT T.HOUR,
COUNT(A.ANIMAL_ID) AS COUNT
FROM (
SELECT 0 AS HOUR UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3
UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7
UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11
UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15
UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19
UNION ALL SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23
) T
LEFT JOIN ANIMAL_OUTS A ON T.HOUR = HOUR(A.DATETIME)
GROUP BY T.HOUR
ORDER BY T.HOUR;
`UNION ALL`은 SQL에서 두 개 이상의 SELECT 결과를 수직으로 합쳐주는 도구이다. 데이터를 누락 없이 모두 합치고 싶을 때 주로 사용한다.
일정 관리 앱
댓글
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record ScheduleResponse(
Long id,
String title,
String content,
AuthorResponse author,
LocalDateTime createdAt,
LocalDateTime modifiedAt,
List<SimpleCommentResponse> comments
) {...
일정 조회 시 해당 일정에 달린 댓글도 함께 응답되도록 `comments` 필드를 추가했다. 하지만 댓글이 없을 때도 `"Comment": []` 처럼 빈 배열이 포함되어 응답되는 문제가 있었다. 처음에는 댓글이 있을 때와 없을 때 조건을 설정해주고 각각의 DTO를 통해 응답되도록 만들어야하나 했는데, `@JsonInclude` 어노테이션을 활용해서 빈 리스트를 자동으로 생략되도록 설정해줬다. 이를 통해 댓글이 없을 경우에는 `comments` 필드 자체가 응답에서 제외되도록 개선했다.
| 옵션 | 설명 |
| Include.ALWAYS | 모든 필드 포함 (기본값) |
| Include.NON_NULL | null인 필드만 제외 |
| Include.NON_EMPTY | null, 빈 리스트, 빈 문자열 모두 제외 |
| Include.NON_DEFAULT | 기본값과 같은 필드 제외 |
// 댓글 생성
@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponse> createComment() {}
// 전체 댓글 조회
@GetMapping("/comments")
public ResponseEntity<List<CommentResponse>> findAll() {}
// 개별 댓글 조회
@GetMapping("/comments/{id}")
public ResponseEntity<CommentResponse> findById() {}
// 댓글 수정
@PatchMapping("/comments/{id}")
public ResponseEntity<CommentResponse> update() {}
// 댓글 삭제
@DeleteMapping("/comments/{id}")
public ResponseEntity<Void> delete() {}
댓글 API는 RESTful한 설계를 기반으로 구현해봤다. 댓글은 일정에 종속됨으로 생성 시에는 일정ID를 경로에 포함시키고, 그 외의 조회, 수정, 삭제는 댓글 자체를 리소스로 다루는 방식으로 설계했다. 어떤 코드가 좋은 코드인지 계속 생각하면서 코딩해야겠다는 생각이 들었다.
public class Schedule extends BaseEntity {
...
@OneToMany(mappedBy = "schedule", cascade = CascadeType.REMOVE)
private List<Comment> comments;
...
}
`cascade`는 JPA에서 연관된 엔티티에 대해 특정 작업을 전파할지 여부를 설정하는 옵션이다. 즉 부모 엔티티에 어떤 작업을 하면 자식 엔티티에도 그 작업을 자동으로 적용할 수 있게 해준다. 위처럼 설정하면 일정을 삭제할 때 연결된 댓글들도 함께 삭제된다.
| 타입 | 설명 |
| PERSIST | 부모 엔티티 저장 시 자식도 함께 저장 |
| MERGE | 부모 엔티티 병합 시 자식도 함께 병합 |
| REMOVE | 부모 삭제 시 자식도 함께 삭제 |
| REFRESH | 부모 새로고침 시 자식도 새로고침 |
| DETACH | 부모 detach 시 자식도 detach |
| ALL | 위 모든 작업을 전파 |
마치며
이제 과제에서 페이지 네이션 파트만 남았다. 오늘은 내가 짠 코드를 다듬고 다시 살펴보고 코드를 그대로 연습삼아 따라 쳐보는 시간을 가졌다. 아무 이해도 없이 반복하는 것도 문제겠지만 이론만 익히는 것도 문제라고 느껴진다. 반복을 통해 손에 익으면 기본적인 CRUD는 무리 없이 구현이 가능하리라는 생각이 있었고, 반복 → 이해 → 반복의 흐름으로 해보다가 필요할 때 그 부분을 깊이있게 공부해보는 게 가장 효과적일 거라는 생각이 들었다.