JPQL Update한 객체를 바로 Return 받을 수 있을까 ?
LendRepository.java
// 대여중인 도서 연장 @Modifying @Query("update Lend l SET l.extendStatus = true where l.user.id = :userId AND l.book.isbn13 = :isbn13") void mExtendLend(@Param("userId") Long userId, @Param("isbn13") String isbn13);
→ JPQL update 후 update 한 걸 service 단에서 Response DTO 에 담아서 return 해야해서
void 가 아닌 Lend 객체를 return 하려고 했는데 안됨.
챗 지피티한테 물어보니
JPQL의
update
쿼리는 기본적으로 엔티티를 반환하지 않기 때문에, update
쿼리에서 바로 수정된 엔티티를 반환하는 것은 불가능합니다. JPQL에서 update
는 단순히 영향을 받은 행의 수를 반환하며, 수정된 엔티티는 반환하지 않습니다.하지만,
update
쿼리 이후에 해당 Lend
엔티티를 다시 조회하여 반환할 수 있습니다. 즉, update
쿼리를 실행한 다음에 다시 한 번 select
쿼리를 사용하여 수정된 엔티티를 조회하는 방식으로 해결할 수 있습니다.아래는 예시 코드입니다:
java 코드 복사 @Transactional @Modifying @Query("update Lend l SET l.extendStatus = true where l.user.id = :userId AND l.book.isbn13 = :isbn13") void updateExtendStatus(@Param("userId") Long userId, @Param("isbn13") String isbn13); @Query("select l from Lend l where l.user.id = :userId AND l.book.isbn13 = :isbn13") Lend findUpdatedLend(@Param("userId") Long userId, @Param("isbn13") String isbn13);
그리고 서비스 레이어에서 이를 다음과 같이 처리할 수 있습니다:
java 코드 복사 @Transactional public Lend extendLend(Long userId, String isbn13) { lendRepository.updateExtendStatus(userId, isbn13); // extendStatus를 업데이트 return lendRepository.findUpdatedLend(userId, isbn13); // 업데이트된 Lend 엔티티 반환 }
요약:
@Modifying
과update
쿼리에서는 엔티티를 반환할 수 없으므로, 업데이트 후에 별도의select
쿼리를 통해 수정된 데이터를 다시 조회해야 합니다.
- 트랜잭션을 사용하여
update
와select
가 동일한 트랜잭션 내에서 일어날 수 있도록 처리해야 합니다.
이 방식으로
update
한 후에 수정된 Lend
엔티티를 반환할 수 있습니다.JPQL에서 Update 쿼리는 데이터베이스의 값을 수정하기 위한 것이며,
update문은 기본적으로 변경된 객체를 반환하지 않는다고 한다.
그래서 JPQL에서는 update 쿼리를 수행한 후 select 쿼리를 통해 변경된 객체를 다시 조회해서 변경된 객체를 가져와야 한다.
error: package does not exist

C:\metacoding\springwork\bookbox-api\src\main\java\green\mtcoding\bookbox\book\Book.java:4: error: package green.mtcoding.bookbox.comment does not exist
import green.mtcoding.bookbox.comment.Comment;
^
import 경로 맞게 되어있는데 서버 실행 시 does not exist 에러가 발생하면

세팅 → annotation processors → enable annotation processing 체크하고
Enable annotation processing 옵션을 활성화하는 이유 :
IntelliJ에서
Enable annotation processing
옵션을 활성화하면, 애노테이션 프로세싱을 사용하도록 설정하게 됩니다. 애노테이션 프로세싱이란 컴파일 시점에 애노테이션을 기반으로 추가 코드를 생성하거나, 기존 코드를 수정하는 기능을 제공하는 과정입니다. 이 옵션을 체크하면 주로 다음과 같은 변화가 있습니다:1. Lombok과 같은 라이브러리에서 자동 코드 생성 활성화
- Lombok과 같은 라이브러리를 사용할 때,
@Getter
,@Setter
,@Builder
,@AllArgsConstructor
등의 애노테이션을 붙이면, 해당 애노테이션을 처리해서 자동으로 필요한 메서드나 생성자를 생성합니다.
- 애노테이션 프로세싱이 활성화되지 않으면 이러한 코드를 컴파일 시점에 생성하지 않으므로, 코드를 제대로 작성했더라도 컴파일 오류가 발생할 수 있습니다.
2. 컴파일 시 애노테이션 기반 코드 생성 활성화
- Hibernate Validator, MapStruct, QueryDSL, Dagger와 같은 라이브러리에서도 애노테이션을 사용해 컴파일 시 자동으로 코드를 생성합니다.
- 예를 들어, MapStruct를 사용하여 매핑 메서드를 자동 생성하려면 애노테이션 프로세싱이 필요합니다.
3. 왜 이걸 체크해야 하는가?
- 많은 라이브러리들이 애노테이션을 활용하여 추가적인 코드를 자동 생성합니다. 이러한 라이브러리들이 제대로 동작하려면 애노테이션 프로세서가 활성화되어 있어야 합니다.
- 기본적으로는 애노테이션 프로세서를 활성화하지 않아도 괜찮은 경우도 있지만, 특정 라이브러리를 사용할 때는 반드시 필요합니다. Lombok과 같은 라이브러리가 대표적인 예입니다. 이를 활성화하지 않으면
package does not exist
같은 오류가 발생할 수 있습니다.
결론:
- *
Enable annotation processing
*을 체크하면 컴파일 시 애노테이션 기반으로 추가적인 코드를 자동으로 생성할 수 있게 됩니다. 만약 Lombok이나 다른 애노테이션을 사용하는 라이브러리를 사용하고 있다면, 이를 반드시 활성화해야 코드 생성 및 빌드가 정상적으로 진행됩니다.

gradle → 위 2개 intelliJ IDEA 로 설정해주기
+ 추가로 아래 Gradle JVM 제대로 설정되어있는지 확인하기
그리고 실행하면 돌아간다.
references : https://dmaolon00.tistory.com/entry/Spring-오류-해결-compileQuerydsl-error-error-package-does-not-exist
자동 반납 기능 → @Scheduled 사용
서버가 켜져 있을 때 자동으로 매서드를 실행시키고 싶을 때
여러가지 방법을 사용할 수 있다.
Spring에서 스케줄링 작업을 설정하는 방법 중 하나로
@Scheduled
애노테이션을 사용하는 방법은 매우 편리하고 간단한 방법입니다. 하지만 이 방법에는 장점과 단점이 모두 존재합니다. 또한, @Scheduled
외에도 스케줄링을 설정하는 다른 방법들이 있습니다.@Scheduled 애노테이션을 사용하는 이유 (장점)
- 간편한 설정:
@Scheduled
애노테이션을 메서드에 붙이는 것만으로 손쉽게 주기적인 작업을 설정할 수 있습니다. 추가적인 설정 파일이나 복잡한 구성 없이 바로 사용할 수 있습니다.
java 코드 복사 @Scheduled(cron = "0 0 0 * * *") // 매일 자정에 실행 public void autoReturnBooks() { // 작업 내용 }
- 다양한 스케줄링 옵션 제공:
@Scheduled
는 다양한 스케줄링 방법을 제공합니다.예시:- fixedDelay: 이전 작업이 끝난 후 일정 시간 지연 후 다음 작업 실행.
- fixedRate: 이전 작업이 시작된 시점으로부터 일정 간격으로 작업 실행.
- cron: cron 표현식을 사용하여 스케줄을 설정.
java 코드 복사 @Scheduled(fixedDelay = 5000) // 5초마다 실행 @Scheduled(fixedRate = 10000) // 10초마다 실행
- 빠른 테스트와 유지보수:
- 복잡한 설정 없이 간단히 메서드에 애노테이션을 추가해 테스트하거나 수정할 수 있어 유지보수가 용이합니다.
- 멀티 스레드 지원:
- 여러 개의 스케줄러가 동시에 실행되더라도 스프링의 TaskExecutor가 이를 관리해 주므로 멀티 스레드 환경에서 잘 동작합니다.
@Scheduled 애노테이션을 사용하지 말아야 할 이유 (단점)
- 복잡한 스케줄 관리에 적합하지 않음:
- 단순한 작업에는 적합하지만, 동적 스케줄 변경, 여러 서버 간의 작업 분배, 작업 실패 시 재시도 같은 복잡한 스케줄링 요구 사항에는 적합하지 않습니다.
- 주기적으로 변경되거나 외부 시스템과 연동된 스케줄 설정이 필요할 경우 불편할 수 있습니다.
- 클러스터 환경에서 문제:
- 여러 인스턴스(서버)가 있는 클러스터 환경에서는 모든 인스턴스에서 같은 스케줄이 실행될 수 있습니다. 이로 인해 중복 작업이 발생하거나 리소스가 낭비될 수 있습니다.
- 예를 들어, 스케줄링이 단일 인스턴스에서만 실행되어야 할 경우
@Scheduled
는 직접적으로 이를 제어할 방법이 없습니다.
- 스케줄링 변경이 불편:
- 스케줄 변경을 위해서는 코드를 수정한 후 재배포해야 합니다. 즉, 스케줄 주기를 동적으로 변경하는 것이 불가능합니다.
- 상태 저장이 불가능:
@Scheduled
방식은 상태 저장이 어렵고, 스케줄의 실행 결과를 기록하거나 실패 시 재시도 등의 로직을 구현하기 어렵습니다.
스케줄링을 설정하는 다른 방법
- Quartz Scheduler:
- Quartz는 매우 강력하고 유연한 스케줄링 라이브러리입니다.
@Scheduled
와 달리, Quartz는 클러스터링, 작업 재시도, 영구 저장, 복잡한 스케줄 관리 등 다양한 기능을 제공합니다. - 분산 환경에서도 안정적으로 스케줄을 관리할 수 있습니다.
- 스케줄 작업의 영속성(작업 스케줄을 DB에 저장 가능)을 지원하며, 작업이 중단되거나 실패했을 때 재시도할 수 있는 기능이 있습니다.
- 다양한 트리거(시간 조건)를 지원하여 매우 세밀한 스케줄 관리가 가능합니다.
- 복잡한 설정과 추가적인 의존성 관리가 필요합니다.
- 설정과 코드가 복잡해질 수 있으므로 간단한 작업에는 과도할 수 있습니다.
장점:
단점:
예시:
java 코드 복사 @Bean public JobDetail jobDetail() { return JobBuilder.newJob(MyJob.class) .withIdentity("myJob") .storeDurably() .build(); } @Bean public Trigger trigger() { return TriggerBuilder.newTrigger() .withIdentity("myTrigger") .withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")) .forJob(jobDetail()) .build(); }
- Spring TaskScheduler:
- Spring에서 제공하는
TaskScheduler
인터페이스를 사용하면@Scheduled
애노테이션 없이 프로그래밍 방식으로 스케줄을 관리할 수 있습니다. - 동적으로 스케줄을 변경하거나 실행할 수 있는 장점이 있습니다.
- 동적으로 작업을 추가하거나 삭제할 수 있습니다.
- 더 많은 제어권을 제공하며,
@Scheduled
보다 유연한 방식으로 스케줄을 관리할 수 있습니다. - 코드가 더 복잡해질 수 있습니다.
장점:
단점:
예시:
java 코드 복사 @Bean public TaskScheduler taskScheduler() { return new ConcurrentTaskScheduler(); // 기본 TaskScheduler 구현체 } public void scheduleTask() { taskScheduler().schedule(new RunnableTask(), new CronTrigger("0 0 0 * * *")); }
- Externally Managed Schedulers:
- Kubernetes CronJobs 또는 AWS Lambda와 같은 외부 시스템의 스케줄링 기능을 사용하여 서버 외부에서 작업을 스케줄링할 수 있습니다.
- 이를 통해 서버에 부담을 주지 않고 스케줄을 관리할 수 있으며, 관리 도구를 사용해 쉽게 모니터링할 수 있습니다.
- 클러스터 환경이나 서버 리소스를 최적화할 수 있으며, 외부 시스템에서 쉽게 관리할 수 있습니다.
- 서버에 종속적이지 않고, 분산 시스템에 적합한 스케줄링을 할 수 있습니다.
- 외부 도구와의 통합이 필요하므로 추가적인 설정이나 복잡성이 있을 수 있습니다.
장점:
단점:
결론:
@Scheduled
는 간단한 스케줄 작업에 적합하고, 코드 수정 없이 빠르게 주기적 작업을 설정하는 데 유용합니다.
- 그러나 복잡한 스케줄링 요구 사항이 있거나 클러스터링 환경에서 안정적인 작업 관리가 필요할 경우에는 Quartz나 외부 스케줄러를 사용하는 것이 더 적합합니다.
- Spring의
TaskScheduler
를 사용하면 동적으로 스케줄 작업을 추가할 수 있어 유연한 스케줄링 관리가 가능합니다.
Quartz 사용을 고려해보았지만
지금 우리 프로젝트에서 스케줄 작업을 하는건 자동반납 기능밖에 없고
개발 기간이 짧아 빠르게 테스트와 유지보수가 가능해야 했기 때문에
Spring 프레임워크에 내장된 @Scheduled 를 사용해보았다. ( ← Spring의 Task 모듈에서 제공 )
어노테이션 사용을 위해

- 먼저 애플리케이션의 설정 클래스에 @EnableScheduling 어노테이션 추가

- 스케줄링할 메서드에 @Scheduled 어노테이션 붙이기
@Scheduled(cron = "0 0 0 * * *")
→ 자정에 실행시키도록 하는 cron이 있어서 이걸 사용했다.

- application.properties 파일에 서버 측에서 사용하는 기본 시간대를 서울로 설정하는 옵션 추가.
( → 스케줄링 작업 시, spring.mvc.timezone을 설정하지 않으면, 기본적으로 UTC 시간대가 적용되어서
데이터 베이스에 현재 한국/서울 시간이 저장되지 않는다. )
아니면
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;INIT=SET TIMEZONE 'Asia/Seoul'
이렇게 H2 데이터베이스에 시간대를 명시적으로 지정해 줄 수도 있다.
- 추가 )
spring.jackson.time-zone
과spring.mvc.timezone
의 차이
spring.jackson.time-zone
과 spring.mvc.timezone
의 차이
Spring Boot의
application.properties
파일에서 설정하는 두 가지 속성인 spring.jackson.time-zone
과 spring.mvc.timezone
는 각각 다른 용도로 사용되며, 주로 시간대(Time Zone)를 다루는 방법에서 차이가 있습니다. 이 두 속성의 역할을 구체적으로 살펴보겠습니다:1. spring.jackson.time-zone
- 역할: JSON 직렬화 및 역직렬화 시 Jackson 라이브러리가 사용할 시간대를 설정하는 옵션입니다.
- 적용 범위: 이 설정은 주로 HTTP 요청/응답 바디에서 시간 데이터를 직렬화하거나 역직렬화할 때, 즉 JSON 형식의 데이터를 주고받을 때 적용됩니다.
- 사용 시점: 서버에서 Date, LocalDateTime 같은 시간 데이터를 JSON으로 변환하거나 JSON 데이터를 시간 객체로 변환할 때, 지정된 시간대로 처리합니다.
properties 코드 복사 spring.jackson.time-zone=Asia/Seoul
이 설정을 사용하면 서버가 JSON으로 응답을 보낼 때
Asia/Seoul
시간대를 기준으로 시간 데이터를 직렬화하며, 클라이언트로부터 받은 시간 데이터도 해당 시간대로 역직렬화하게 됩니다.2. spring.mvc.timezone
- 역할: Spring MVC에서 서버 측에서 사용하는 기본 시간대를 설정하는 옵션입니다.
- 적용 범위: 이 설정은 Spring MVC에서 컨트롤러가 처리하는 요청에서 날짜와 시간을 다룰 때 사용됩니다. 주로 웹 애플리케이션 전반에서 시간대를 다룰 때 기본적으로 사용할 시간대를 지정합니다.
- 사용 시점: 서버가 사용자 요청을 처리할 때, 또는
@RequestParam
,@ModelAttribute
같은 방법으로 시간 관련 데이터를 받아 처리하는 곳에서 이 시간대를 기준으로 처리하게 됩니다.
properties 코드 복사 spring.mvc.timezone=Asia/Seoul
이 설정을 사용하면 Spring MVC 애플리케이션의 모든 시간대 관련 작업에서
Asia/Seoul
을 기본으로 적용합니다. 예를 들어, 사용자가 시간을 입력하여 폼을 제출할 때 이 시간대가 기준이 됩니다.차이점 요약
spring.jackson.time-zone
: JSON 직렬화 및 역직렬화에서 Jackson이 사용할 시간대를 설정합니다. 주로 API 통신 시 JSON 형태의 데이터에 적용됩니다.
spring.mvc.timezone
: Spring MVC 애플리케이션 전반에서 시간 데이터를 처리할 때 사용할 기본 시간대를 설정합니다. 주로 서버 측에서 시간대 관련 처리에 영향을 미칩니다.
따라서, **
spring.jackson.time-zone
**은 JSON 데이터의 직렬화/역직렬화 시 사용되는 Jackson 라이브러리에 적용되고, **spring.mvc.timezone
**은 MVC 레이어에서 시간 데이터를 다룰 때 적용됩니다.깃허브에만 있는 브랜치 가져오기
집에서 작업하면서 로컬에 user/join-check 브랜치를 만들었고, 작업 후 깃허브에 push 했다.
( → 아직 dev 브랜치에 merge 는 되지 않은 상태이다. )
그리고 다음날 학원에서 이어서 작업하기 위해서 dev 브랜치로 이동해서
git pull origin dev 를 해주었고, 이어서 git checkout user/join-check 를 하려고 하니까

error: pathspec 'user/join-check' did not match any file(s) known to git
이런 에러가 발생했다.
찾아보니
”이 에러 메시지는
user/join-check
라는 브랜치가 원격 저장소(GitHub) 또는 로컬 저장소에서 현재 존재하지 않음을 의미합니다. 즉, Git이 해당 브랜치를 찾을 수 없다는 뜻입니다.”
→ 아직 원격 저장소에 생성된 브랜치가 로컬에 안 만들어져서 발생한 에러였다.
그래서 로컬의 dev 브랜치에서
- git fetch origin
- git checkout -b user/join-check origin/user/join-check
을 해주니 origin/user/join-check 을 트래킹해서 로컬에도 user/join-check 브랜치가 생겼다!

어제 집에서 user/join-check 브랜치에 push 한 내용도 현재 학원 로컬에 잘 받아져 있다.
도움 : 챗지피티
현재 상황을 보면 1번 컴퓨터에서
user/join-check
라는 브랜치를 만들고, GitHub에 푸시한 상태입니다. 그러나 2번 컴퓨터에서는 user/join-check
라는 브랜치가 아직 로컬에 존재하지 않아서 checkout
명령어를 실행했을 때 "pathspec 'user/join-check' did not match any file(s) known to git"라는 에러가 발생한 것입니다.이럴 때는 로컬에서 브랜치를 새로 만들 필요는 없습니다. 이미 GitHub에
user/join-check
브랜치가 존재하기 때문에, 로컬에 해당 브랜치를 가져오는 방법으로 해결할 수 있습니다.해결 방법:
- 브랜치 목록을 업데이트하기 위해 아래 명령어를 사용하세요:
git fetch origin
- 그리고 원격에 있는 브랜치를 체크아웃하면서 로컬에도 동일한 브랜치를 생성하려면, 다음 명령어를 사용하세요:
git checkout -b user/join-check origin/user/join-check
이 명령어는 원격에 있는
user/join-check
브랜치를 로컬로 가져오고, 로컬에서도 동일한 브랜치로 체크아웃해줍니다. 이렇게 하면 로컬에도 동일한 브랜치가 생기고, 그 후부터는 해당 브랜치에서 작업할 수 있습니다.이제 필요한 경우
git pull
을 해서 최신 상태로 유지할 수 있습니다.git bash 에서 커밋 메시지 작성할때 오타

git bash 에서 commit 메시지 작성할 때 오타가 나거나 띄어쓰기 잘못하면 이상한 문자가 들어가는데
깃허브에 push 하면 이렇게 이상한 문자가 들어간채로 올라간다.
PR요청할 때 Add a title 부분을 잘 적고 dev에 merge 하면
dev에는 정상적인 커밋 메시지가 뜨지만
기존에 push한 브랜치에는 이상한 문자가 들어가있다.
→ 깔끔하게 유지하려면 커밋할 때 한번에 잘 적어야 할 거 같다.
→ 이미 커밋된 이후라면 Commit 메시지를 수정하는 방법을 사용할 수 있다.
- 마지막 Commit 메시지 수정하기 → git commit —amend -m “바꿀 메시지”
- Commit 메시지 여러개 수정하기 →
- git rebase -i HEAD~3 ( ⇒ 여기서 마지막 숫자는 불러올 커밋수. 최근순 )
- 메시지를 수정하고 싶은 Commit 내역의 pick을 reword로 바꾸고 : 와 wq를 눌러서 터미널을 종료
- 메시지를 수정하고 wq로 저장
- Remote에 이미 push 했을 경우 → 커밋 메시지 수정 후 강제 push 하는 방법이 있지만 권장되지 않음
references :
반납일에 해당하는 도서 select 하기
자동 반납 기능에는 반납일에 해당하는 도서만 List<Lend> 에 담기도록 쿼리를 작성해야 한다.
@Query("SELECT l FROM Lend l WHERE FUNCTION('DAYOFMONTH', l.returnDate) = FUNCTION('DAYOFMONTH', CURRENT_TIMESTAMP) AND l.returnStatus = false") List<Lend> mFindAllByReturnDateAndReturnStatusFalse();
→ 처음에 day만 비교하는 쿼리를 사용했는데, 그러니 오늘이 10월 16일 이면
9월 16일, 11월 16일 것도 조회되는 대참사가….
→ 12시에 오늘 “일(day)”로 바뀌었을 때 날짜를 비교하는 것 까지는 맞지만
“연-월-일”을 모두 비교해야지 정확한 결과가 나온다.
@Query("SELECT l FROM Lend l WHERE CAST(l.returnDate AS date) = CURRENT_DATE AND l.returnStatus = false") List<Lend> mFindAllByReturnDateAndReturnStatusFalse();
→ “연-월-일”을 모두 비교해서 일치하는 것만 조회하도록 수정하니 정확한 결과가 나온다.
@Query("SELECT l FROM Lend l WHERE FUNCTION('DATE', l.returnDate) = CURRENT_DATE AND l.returnStatus = false") List<Lend> mFindAllByReturnDateAndReturnStatusFalse();
중간에 아래와 같이 쿼리를 작성해서 실행해보았는데,
H2는
DATE()
함수 대신 다른 방식을 사용해야 한다고 해서
CAST()
또는 TRUNC()
함수로 날짜 부분만 추출하도록 했다.
이 에러는 H2 데이터베이스에서
DATE()
함수가 지원되지 않아서 발생한 것입니다. H2는 DATE()
함수 대신 다른 방식을 사용해야 하며, returnDate
필드를 날짜 부분만 추출하여 비교하는 방법을 사용할 수 있습니다.해결 방법:
H2에서는
CAST()
또는 TRUNC()
함수로 날짜 부분만 추출할 수 있습니다. CAST()
를 사용하여 returnDate
의 날짜 부분만 가져오는 방식으로 쿼리를 수정해 보세요:java 코드 복사 @Query("SELECT l FROM Lend l WHERE CAST(l.returnDate AS date) = CURRENT_DATE AND l.returnStatus = false") List<Lend> mFindAllByReturnDateAndReturnStatusFalse();
위 쿼리는
returnDate
를 date
로 캐스팅하여 CURRENT_DATE
와 비교하도록 변경되었습니다. H2와 같은 내장 데이터베이스는 특정 함수나 쿼리 구문에서 차이가 있을 수 있으므로, 이러한 방식으로 쿼리를 조정하면 해결될 가능성이 큽니다.만약
CAST()
도 작동하지 않으면, returnDate
의 YEAR()
, MONTH()
, DAY()
함수들을 조합해서 직접 날짜를 비교하는 방법을 사용할 수도 있습니다.ResponseDTO의 특정 필드 JSON 응답에서 제외시키기

→ 로그인 기능 구현 초기 코드인데, JWT를 만들어서 Controller로 전달하기 위해
UserResponse.LoginDTO에 담았다.

→ 로그인DTO는 이렇게 생겼는데 accessToken이 필드로 들어가 있다.

→ UserController단 에서
먼저, LoginDTO의 accessToken을 꺼내서 응답헤더에 넣어주었고
LoginDTO는 응답body에 넣어서 그대로 return 해주었다.


그렇게 하니 응답 헤더에도 토큰이 들어가고 응답 바디에도 토큰이 들어간다
→ 컨트롤러에서 JSON 응답으로 보낼 때 accessToken을 제외시키고 싶은데 어떻게 해야할까 ?

→ 이렇게 ResponseDTO에서 @JsonIgnore 어노테이션을 달아주면
해당 필드를 JSON 직렬화 또는 역직렬화 과정에서 무시하게 해준다.
→ 서비스에서 컨트롤러로 넘어갈 때는 값이 전달되지만
클라이언트에게 전달하기 위해 JSON으로 응답 보낼때는 제외되는 것!


→ 응답 헤더로만 토큰이 전달 된 것을 확인할 수 있다 : )
@JsonIgnore
애노테이션은 Jackson 라이브러리에서 제공하는 애노테이션으로, 특정 필드나 메서드를 JSON 직렬화 또는 역직렬화 과정에서 무시하도록 설정하는 데 사용됩니다. 이를 통해 특정 데이터를 클라이언트에 노출시키지 않거나, 역직렬화 시 특정 필드를 무시할 수 있습니다.@JsonIgnore 주요 사용 목적
- JSON 직렬화 과정에서 필드 제외
- 객체를 JSON으로 변환할 때 특정 필드를 포함하고 싶지 않을 때 사용합니다. 예를 들어, 비밀번호와 같은 민감한 정보는 클라이언트에게 노출하지 않도록 설정할 수 있습니다.
- JSON 역직렬화 과정에서 필드 제외
- 클라이언트로부터 JSON 데이터를 받을 때 특정 필드는 무시하고 싶을 때 사용합니다. 예를 들어, 클라이언트가 값을 변경할 수 없게 하기 위해, 역직렬화 시 특정 필드의 값은 무시할 수 있습니다.
사용 방법
1. 필드에 적용
필드에
@JsonIgnore
를 붙이면 그 필드는 JSON 응답에서 제외됩니다. 직렬화 시 해당 필드는 JSON에 포함되지 않으며, 역직렬화 시에도 무시됩니다.java 코드 복사 import com.fasterxml.jackson.annotation.JsonIgnore; public class User { private String username; @JsonIgnore private String password; // 비밀번호 필드를 제외 // getters and setters }
위의 예시에서
password
필드에 @JsonIgnore
를 붙였기 때문에, 객체를 JSON으로 변환할 때 password
필드는 JSON 응답에 포함되지 않습니다.2. 메서드에 적용
@JsonIgnore
를 getter 메서드에 붙이면 해당 필드가 JSON으로 변환될 때 제외됩니다.java 코드 복사 import com.fasterxml.jackson.annotation.JsonIgnore; public class User { private String username; private String password; @JsonIgnore public String getPassword() { // getter에 @JsonIgnore 적용 return password; } // getters and setters }
3. 역직렬화에서 무시
클라이언트로부터 데이터를 받을 때도
@JsonIgnore
는 적용됩니다. 역직렬화 시에도 해당 필드는 무시되며, 데이터가 전달되더라도 해당 필드의 값은 변경되지 않습니다.java 코드 복사 import com.fasterxml.jackson.annotation.JsonIgnore; public class User { private String username; @JsonIgnore // 이 필드는 역직렬화 시 무시됨 private String password; // getters and setters }
위의 코드에서 클라이언트가
password
값을 JSON으로 보내도, 해당 값은 무시되고 기존 객체의 값에 영향을 주지 않습니다.@JsonIgnore 관련 추가 애노테이션
@JsonIgnoreProperties
: 클래스 단위에서 특정 필드들을 무시할 때 사용합니다. 여러 필드를 한 번에 무시하고 싶을 때 유용합니다.
java 코드 복사 @JsonIgnoreProperties({"password", "email"}) public class User { private String username; private String password; private String email; // getters and setters }
위 코드에서는
password
와 email
필드가 JSON 변환에서 무시됩니다.@JsonIgnoreType
: 특정 타입 전체를 무시하고 싶을 때 사용합니다. 이 타입에 속하는 모든 객체가 직렬화 과정에서 무시됩니다.
java 코드 복사 @JsonIgnoreType public class SensitiveData { // 이 클래스는 직렬화/역직렬화 과정에서 완전히 무시됨 }
사용 시 주의사항
@JsonIgnore
를 사용하면 해당 필드나 메서드는 직렬화와 역직렬화 모두에서 제외됩니다. 만약 직렬화에서만 제외하고, 역직렬화에서는 사용하고 싶다면, 대신@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
같은 애노테이션을 고려할 수 있습니다.
java 코드 복사 @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String password;
이 경우,
password
는 클라이언트로부터 값을 받을 수 있지만, JSON 응답에서는 제외됩니다.정리
@JsonIgnore
는 주로 객체를 JSON으로 변환할 때 보안상의 이유로 특정 데이터를 숨기거나, 역직렬화 시 불필요한 데이터를 무시하기 위해 사용됩니다. 상황에 따라 적절하게 사용하면 데이터 노출을 방지하고, 역직렬화 시 불필요한 처리를 줄일 수 있습니다.error: 대여중인 도서 반납 기능 에러 해결
LendService
// book_Tb -> 대여상태 false, 대여수 -1 // lend_tb -> 반납한 일자 (now), 반납상태 -> true @Transactional public LendResponse.ReturnDTO 직접반납하기(Long userId, LendRequest.ReturnDTO request) { // 1. 대여상태인지 확인 Boolean b = bookRepository.mCheckLendStatus(request.getIsbn13()).orElseThrow(() -> new ExceptionApi404("요청하신 도서가 존재하지 않습니다.")); if (!b) { throw new ExceptionApi404("대여중인 도서가 아닙니다."); } // 2. booktb 대여 상태 바꾸기 int updateCount = bookRepository.mUpdateLendStatusAndCountReturn(request.getIsbn13()); // 업데이트가 성공했는지 확인 (1이 아니면 실패) if (updateCount != 1) { throw new ExceptionApi500("도서 반납 처리 중 문제가 발생했습니다."); } // 3.lend_tb 대여 상태 바꾸기 int returnStatus = lendRepository.mReturnLend(userId, request.getIsbn13()); // 업데이트 성공했는지 확인 (1이 아니면 실패) if (returnStatus != 1) { throw new ExceptionApi500("도서 반납 처리 중 문제가 발생했습니다."); } // 4. 반납정보 return Lend lendPS = lendRepository.mFindLend(userId, request.getIsbn13()); return new LendResponse.ReturnDTO(lendPS); }
→ 처음에 구현한 “도서반납하기” 기능의 서비스 레이어 코드.
마지막 4. 반납정보 return 부분의 mFindLend 의 쿼리는
LendRepository
// 해당 user와 book의 lend 데이터 조회 @Query("select l from Lend l where l.user.id = :userId AND l.book.isbn13 = :isbn13") Lend mFindLend(@Param("userId") Long userId, @Param("isbn13") String isbn13);
이렇게 user_id와 isbn13(book의 pk)를 가지고 lend 데이터 조회하는 쿼리다.
→ 해당 유저가 여러번 해당 도서를 빌리고 반납했다면 lend_tb에 그 히스토리가 남기때문에 ( → return_status = false, true 로 반납된 도서인지 아닌지 구분하기 때문에 lend_tb에 대여한 도서 내역과 지금 대여중인 도서 내역이 전부다 들어가 있다. )
userId와 isbn13으로 조회하면 하나의 객체가 아니라 List가 출력될텐데
그 부분을 생각하지 못했다.

→ 기존에 빌린 내역이 없는 책은 이렇게 성공적으로 반납이 되어서 잘 되는줄 알았다.
하지만 기존에
9791190669238
이 도서를 빌린 내역이 있으면
{"reason": Query did not return a unique result: 2 results were returned}
이렇게 List로 받지 못해서 에러가 터진다.
→ 그래서 service 레이어 에서 반납정보를 찾아오는 쿼리를 수정했다.
LendRepository
// 가장 최근에 반납한 도서 1개를 select 하거라 @Query(value = "SELECT * FROM lend_tb WHERE user_id = :userId AND book_id = :isbn13 AND return_status = true ORDER BY return_date DESC LIMIT 1", nativeQuery = true) Optional<Lend> findLatestReturnedLendNative(@Param("userId") Long userId, @Param("isbn13") String isbn13);
: 대여 내역중에 가장 최근에 반납한 ( = 방금 반납한 그 반납내역 ) 것만 select 하는 쿼리 !
ORDER BY return_date DESC LIMIT 1
를 추가해서 return_date를 내림차순으로 정렬해서 그 중 맨 윗단에 있는 1개 ( = 방금 반납한 그 반납내역 )를
지정하도록 했다.
LendService
// 5. 반납정보 return Lend lendPS = lendRepository.findLatestReturnedLendNative(userId, request.getIsbn13()) .orElseThrow(() -> new ExceptionApi404("반납된 기록을 찾을 수 없습니다."));
방금 만든걸로 바꿔줬다.
LendService
// book_Tb -> 대여상태 false, 대여수 -1 // lend_tb -> 반납한 일자 (now), 반납상태 -> true @Transactional public LendResponse.ReturnDTO 직접반납하기(Long userId, LendRequest.ReturnDTO request) { // 1. 대여상태인지 확인 Boolean b = bookRepository.mCheckLendStatus(request.getIsbn13()).orElseThrow(() -> new ExceptionApi404("요청하신 도서가 존재하지 않습니다.")); if (!b) { throw new ExceptionApi404("대여중인 도서가 아닙니다."); } // 2. booktb 대여 상태 바꾸기 int updateCount = bookRepository.mUpdateLendStatusAndCountReturn(request.getIsbn13()); // 업데이트가 성공했는지 확인 (1이 아니면 실패) if (updateCount != 1) { throw new ExceptionApi500("book도서 반납 처리 중 문제가 발생했습니다."); } // 3.lend_tb 대여 상태 바꾸기 int returnStatus = lendRepository.mReturnLend(userId, request.getIsbn13()); // 업데이트 성공했는지 확인 (1이 아니면 실패) if (returnStatus != 1) { throw new ExceptionApi500("lend도서 반납 처리 중 문제가 발생했습니다."); } // 4. 반납 후 예약자가 있는지 확인하여 처리 - TODO: 신민재 boolean hasReservations = reservationRepository.countCurrentReservations(request.getIsbn13()) > 0; if (hasReservations) { // 첫 번째 예약자에게 자동 대여 처리 reservationService.자동대여(request.getIsbn13()); } // 5. 반납정보 return Lend lendPS = lendRepository.findLatestReturnedLendNative(userId, request.getIsbn13()) .orElseThrow(() -> new ExceptionApi404("반납된 기록을 찾을 수 없습니다.")); return new LendResponse.ReturnDTO(lendPS); }
⇒ “반납 기능” 서비스 레이어 최종 코드
그리고 나서 기존
9791190669238
도서의 대여 내역을 여러개 만들고 이 있는 도서를 다시 빌린 후 반납해야하는 상황에서 반납하기를 요청하면

→ 반납이 잘 된다!
Share article