이건 누구 보라고 잘 정리해놓은 글보다는 삽질하면서 기록해놓은 글이다. (그래서 문제가 발생했던 메서드의 상세한 코드나 에러 로그 전문은 첨부하지 않았다.)
JPA를 사용하여 기능을 구현하는데 원하는 순서대로 실행이 되지 않아서 Hibernate 로그와 내가 찍은 로그를 보면서 연구를 해봤다..
이거 때문에, 로그인을 몇 번을 했는지, 몇 개의 로그를 찍은건지 모르겠다.....
혹시혹시나 나와 같은 의문으로 글을 찾아보고 있는 사람들을 위해 잠정적으로 내린 결론부터 말해본다. 어차피 아래에도 적을거지만, 아래와 같이 결론을 내렸다.
"JpaRepository의 실행순서와 Hibernate의 로그 출력 시점은 다를 수 있다. delete 메서드는 아이디를 가진 데이터 조회 후, 해당 데이터를 삭제하는 과정을 거치기 때문에, 조회나 저장보다 시간이 오래 걸리는 듯하다. 그래서 조회, 저장 쿼리문보다 빨리 수행되지만, 수행 완료되는 시점이 늦어서 로그가 늦게 찍힐 수 있다."
나는 분명 아래의 순서와 같이 코드를 짰다.
1. 기존 데이터 삭제
2. A 테이블에 데이터 저장
3. B 테이블에 데이터 저장
4. C 테이블에서 데이터 조회
5. C 테이블에 데이터 저장 or 수정
근데 쿼리 로그를 보면 아래 순서대로 순서가 뒤죽박죽 실행이 됐다.
2(저장) → 3(저장) → 5(저장 or 수정) → 1(삭제) → 4(조회)
그래서 여기서 한 가지 생각이 들었다.
"그럼 Hibernate 로그가 찍히는 순간과 실제 데이터베이스 연산이 일어나는 시점이 다른걸까?"
위의 생각을 확인하기 위해, 정확히 데이터가 언제 삭제되는지 알고 싶었다. 그래서 로그를 찍어봤는데, 로그를 찍다보니 또 의문이 들었다.
위에서 언급한 것처럼 원래 1번이 가장 먼저기 때문에 delete 쿼리문부터 로그가 찍혀야하는데, 보면 insert into date_tune...
(2번 과정)이 찍힌다.
70: 20
43: 20
이렇게 찍힌 로그는 데이터가 언제 삭제되는지, 제대로 삭제됐는지에 대한 정보를 알아보기 위해 찍은 로그인데, 삭제한 20번 데이터를 자꾸 정상정으로 데이터 정보를 가져온다.
log.info("46: {}", dateTuneRepository.getById(20L).getDateTuneNo());
그래서 데이터가 삭제되지 않는건가 싶어서 아래 예시처럼 count 메소드로 레코드 갯수 정보를 로그로 찍어봤다.
log.info("41: {}", dateTuneRepository.count());
근데, 또 그러면 delete 쿼리가 순서에 맞게 바로 찍힌다. 왜 로그 하나 차이인데 어쩔때는 delete 로그가 빨리 찍혔다가 어쩔때는 나중에 찍혔다가...도대체 쿼리 로그는 언제 찍히는것인가....
getById로 로그를 찍다가 한 가지 알게 된 점이 있다. JpaRepository의 구현 클래스인 SimpleJpaRepository를 보면 아래와 같이 메소드가 구현되어 있다.
@Override
public T getById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
@Override
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = new HashMap<>();
getQueryHints().withFetchGraphs(em).forEach(hints::put);
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}
둘 다 id를 이용해 데이터베이스에서 해당 id를 가진 객체를 찾는 행동을 한다는 점에서는 비슷하다.
다만 getById
는 entity자체를 반환하고, findById
는 Optional 객체를 반환한다.
또한, 해당 id를 가진 데이터를 찾을 때 getById는 entity manger의 getReference
를 사용하고, findById는 find
메소드를 사용한다.
getReference는 엔티티를 반환하는데, 찾는 엔티티가 없으면, EntityNotFoundException
을 반환한다.
find는 엔티티 또는 null을 반환한다.
하지만 아무리 찾아봐도, 삭제된 20번의 데이터를 getById(20L)
로 가져오려고 할 때, EntityNotFoundException
이 발생하는 게 아니라, 정상적으로 데이터를 가져오는지는 이유를 잘 모르겠다. 왜냐하면, 다른 테이블에서도 삭제된 데이터를 똑같은 방법으로 가져와보는 테스트를 했는데, 그건 예상대로 예외가 발생했기 때문이다. 왜 이 테이블만 그런건지...아무래도 getById를 호출한 시점이나 장소?에 따라 다른게 아닐까...
그래서 나 나름대로 삽질을 하면서 잠정적으로 결론을 내렸다.
"JpaRepository의 실행순서와 Hibernate의 로그 출력 시점은 다를 수 있다. delete 메서드는 아이디를 가진 데이터 조회 후, 해당 데이터를 삭제하는 과정을 거치기 때문에, 조회나 저장보다 시간이 오래 걸리는 듯하다. 그래서 조회, 저장 쿼리문보다 빨리 수행되지만, 로그가 늦게 찍힐 수 있다."
이 글 아래 첨부한 로그는 위에서 잠시 언급했던 방법으로, JpaRepository의 count() 메서드로 삭제 시점을 확인했다.
삭제하기 전 데이터 개수는 8개이지만, 삭제 후 7개로 줄어든다. count()로 로그를 찍지 않았을 때와 count()로 로그를 찍었을 때, Hibernate 로그 순서가 다르긴 하지만, 일단은 삭제는 내가 원하는 시점에 바로 수행되는 게 맞는 같다.
2022-08-03 14:45:27.338 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 41: 8
Hibernate:
select
datetune0_.date_tune_no as date_tun1_1_,
datetune0_.av_date as av_date2_1_,
datetune0_.schedule_no as schedule3_1_,
datetune0_.user_no as user_no4_1_
from
date_tune datetune0_
where
datetune0_.user_no=?
and datetune0_.schedule_no=?
and datetune0_.av_date=?
2022-08-03 14:45:27.362 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 🔽 36번 데이터(8번 스케줄, 7번 유저, 2022-08-01) 에 해당하는 데이터 삭제를 시작합니다.🔽
Hibernate:
delete
from
date_tune
where
date_tune_no=?
Hibernate:
select
count(*) as col_0_0_
from
date_tune datetune0_
2022-08-03 14:45:27.406 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 75: 7
Hibernate:
select
count(*) as col_0_0_
from
date_tune datetune0_
2022-08-03 14:45:27.422 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 43: 7
Hibernate:
insert
into
date_tune
(av_date, schedule_no, user_no)
values
(?, ?, ?)
2022-08-03 14:45:27.437 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 가능 날짜 저장 완료
Hibernate:
select
count(*) as col_0_0_
from
date_tune datetune0_
2022-08-03 14:45:27.445 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 46: 8
Hibernate:
insert
into
av_time
(av_time, date_tune_no)
values
(?, ?)
2022-08-03 14:45:27.456 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 가능 시간 저장 완료
Hibernate:
select
count(*) as col_0_0_
from
date_tune datetune0_
2022-08-03 14:45:27.471 INFO 66848 --- [io-8080-exec-10] com.sswu.meets.service.DateTuneService : 51: 8
사실 알게 된 게 모두 맞는지는 모르겠지만, 로그를 찍어보면서 알게 된 점들을 정리한다.
EntityNotFoundException
을 발생시키고, findById는 Entity 또는 null을 반환한다.