Today's Codekata
// 최소 직사각형
class Solution {
public int solution(int[][] sizes) {
int maxW = 0;
int maxH = 0;
for (int i = 0; i < sizes.length; i++) {
int w = Math.max(sizes[i][0], sizes[i][1]);
int h = Math.min(sizes[i][0], sizes[i][1]);
maxW = Math.max(maxW, w);
maxH = Math.max(maxH, h);
}
return maxW * maxH;
}
}
// 시저 암호
class Solution {
public String solution(String s, int n) {
String answer = "";
for (char c : s.toCharArray()) {
if (Character.isUpperCase(c)) {
answer += (char) ((c - 'A' + n + 26) % 26 + 'A');
} else if (Character.isLowerCase(c)) {
answer += (char) ((c - 'a' + n + 26) % 26 + 'a');
} else {
answer += c;
}
}
return answer;
}
}
`String alphabet = "abcde..."` 이런 문자열도 만들어보고 여러 방향으로 답을 찾아봤었는데, 다른 사람들의 답을 참고해야했다.
`((c - 'A' + n + 26) % 26 + 'A')` 이 부분이 문제였는데, 풀어보면 c가 'D'고 이동거리가 3이라고 가정해보면
`'D' - 'A' = 3` → `2 + 3 + 26 = 31` → `31 % 26 = 5` → `5 + 'A' = 'F'` 이런 식으로 풀어진다. +26은 음수 방지를 위한 보정값이다.
-- 조건에 맞는 도서와 저자 리스트 출력하기
SELECT B.BOOK_ID, A.AUTHOR_NAME, DATE_FORMAT(B.PUBLISHED_DATE, '%Y-%m-%d') AS PUBLISHED_DATE
FROM BOOK B
INNER JOIN AUTHOR A ON B.AUTHOR_ID = A.AUTHOR_ID
WHERE B.CATEGORY = '경제'
ORDER BY B.PUBLISHED_DATE
-- 조건별로 분류하여 주문상태 출력하기
SELECT ORDER_ID, PRODUCT_ID, DATE_FORMAT(OUT_DATE, '%Y-%m-%d') AS OUT_DATE,
CASE WHEN OUT_DATE <= '2022-05-01' THEN '출고완료'
WHEN OUT_DATE > '2022-05-01' THEN '출고대기'
WHEN OUT_DATE IS NULL THEN '출고미정' END AS '출고여부'
FROM FOOD_ORDER
ORDER BY ORDER_ID
-- 성분으로 구분한 아이스크림 총 주문량
SELECT I.INGREDIENT_TYPE,
SUM(F.TOTAL_ORDER) AS TOTAL_ORDER
FROM FIRST_HALF F
INNER JOIN ICECREAM_INFO I ON F.FLAVOR = I.FLAVOR
GROUP BY 1
ORDER BY 2
Today I Learned
일정 관리 앱 만들기
JPA Repository란?
ORM(Object-Relational Mapping)은 객체와 관계형 데이터베이스(RDB)의 테이블을 자동으로 매핑해주는 기술이다.
JPA(Java Persistence API)는 자바에서 ORM을 구현한 대표적인 기술로, 객체를 RDB에 저장하고 조회할 수 있게 해준다.
JpaRepository는 Spring에서 JPA를 쉽게 사용할 수 있도록 만든 SQL 없이도 CRUD 작업을 손쉽게 처리할 수 있다.
주요 기능
| 기능 | 설명 |
| save() | 엔티티 저장 또는 수정 |
| findById() | ID로 조회 |
| findAll() | 전체 조회 |
| deleteById() | ID로 삭제 |
| existsById() | 존재 여부 확인 |
`findById()` 의 반환타입은 `Optional<T>` 인데, 그 이유는 DB에서 해당 ID의 Entity가 존재하지 않을 수도 있기 때문이다. 그래서 `Optional`을 사용하거나 값이 없으면 예외를 던져주어야 한다.
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
위와 같은 방식이 가장 많이 사용된다고 한다.
// 선택 일정 조회
@Transactional(readOnly = true)
public EventResponseDto getEvent(Long id) {
Event event = eventRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("일정이 존재하지 않습니다."));
return new EventResponseDto(
event.getId(), event.getTitle(), event.getDescription(),
event.getName(), event.getCreatedAt(), event.getModifiedAt());
}
참고해서 ID로 일정을 조회했을 때 일정이 존재하지 않으면 예외를 던지도록 설정했다.
IoC / DI
IoC (Inversion of Control) - 제어의 역전
기존 방식: 개발자가 직접 객체를 생성하고 연결해줘야한다.
IoC 방식: 객체 생성과 연결을 프레임워크(Spring)가 대신 처리한다.
장점: 객체 간 결합도 감소(느슨한 결합: 협력은 하되 의존도는 낮춘다), 코드의 유연성 증가로 테스트와 유지보수가 쉬워진다.
DI (Dependency Injection) - 의존성 주입
IoC를 구현하는 방식 중 하나로, 필요한 객체(의존성)를 외부에서 주입받는 것이다.
DI 방식 3가지 (요즘은 생성자 주입 방식만 주로 사용된다.Spring 공식 권장)
| 방식 | 특징 | 어노테이션 |
| 생성자 주입 | 가장 권장됨, 불변성 보장 | @Autowired 또는 @RequiredArgsConstructor |
| 수정자 주입 | 선택적 주입 가능 | @Autowired + set...() |
| 필드 주입 | 코드 간단하지만 테스트 어려움 | @Autowired |
IoC와 DI와의 관계
| 개념 | 설명 | 역할 |
| IoC | 객체 생성과 생명주기 관리를 개발자가 아닌 Spring 컨테이너가 담당 | "누가 객체를 만들고 관리할까?"에 대한 답 |
| DI | IoC를 구현하는 방식 중 하나로, 필요한 객체를 외부에서 주입 | "그 객체를 어떻게 전달할까?"에 대한 답 |
일정 관리 앱의 예외 처리
// 일정 수정
@Transactional
public void modifyEvent(EventRequestDto requestDto, Long id) {
Event event = eventRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("일정이 존재하지 않습니다."));
if (event.getPassword().equals(requestDto.getPassword())) {
event.updateFromDto(requestDto);
eventRepository.save(event);
} // 비밀번호 오입력 시 어떻게 해야할지?
}
// 일정 삭제
@Transactional
public void removeEvent(String password, Long id) {
Event event = eventRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("일정이 존재하지 않습니다."));
if (event.getPassword().equals(password)) {
eventRepository.deleteById(id);
} // 현재 비밀번호를 오입력해도 200 OK가 뜬다. 삭제는 비밀번호가 맞아야 되긴하다.
}
입력 받은 데이터를 통해서 해당 id의 데이터와 매칭시키는 것과 비밀번호가 일치하는지 조건문을 통해 잘 처리됐으나 상태 코드는 실패일 때도 200 OK로 보여지고 있어서 수정과 삭제가 제대로 동작하고 있는지 확인이 어렵다. Java에서 써봤던 예외를 던지는 방식과는 조금 다른 방법이 필요해보인다.
프로젝트를 진행하며
오늘 Entity부터 dto, controller, service, repository까지 생성해보면서 그간 이론으로 공부했던 것들이 어떤 구조와 흐름으로 동작하는 지 이해할 수 있는 시간이었다. 클라이언트로부터 받은 요청을 요청DTO를 통해 컨트롤러에 전달되고 서비스의 비즈니스 로직들을 거쳐서 repository와 entity를 통해 데이터를 주고 받으며 생성, 조회, 수정, 삭제등의 일정 관리 API를 만들어볼 수 있었다. 처음엔 요청과 데이터 흐름이 어렵게 느껴졌지만, 기능을 하나씩 추가해보며 구조가 점점 명확해졌다. 아직 잘 만들어졌다고는 할 수 없지만 어느 정도 구색을 갖춰졌다. 내일은 더 발전한 결과물이 될 거라 생각한다.