- java
- 스프링
- @transactional
- 자바
- join
- DI
- Django
- sql
- 스프링부트
- 1차원 배열
- AWS
- spring mvc
- SSL
- Docker
- 문자열
- nginx
- PYTHON
- mysql
- string
- spring boot
- spring
- select
- 데이터베이스
- static
- jpa
- 프로그래머스
- spring security 6
- springboot
- ORM
- hibernate
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
개발하는 자몽
[Spring Data JPA] TransactionRequiredException 본문
아래 블로그 글을 참고하여 정리했습니다.
Spring DeleteAllBy...In 호출시 에러 ( TransactionRequiredException )
문제 상황 : deleteAllByIdxIn 호출 시 entitymanager가 왜 없을까? JPA OSIV라면 기본적으로 트랜잭션 범위는 서비스 단까지 있을테고, entity manager는 생성됐을 것이다. 그런데 왜 아래와 같은 에러가 났을까
happyer16.tistory.com
상황
에러 문구
javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call
에러 코드
@Service
@RequiredArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final RefreshTokenRepository rtRepository;
private final RedisUtil redisUtil;
...
public void logout(String accessToken, Long id) {
CustomUserDetails user = userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, NOT_FOUND_USER.name()));
// 에러 발생 코드
rtRepository.deleteRefreshTokenByUsername(user.getUsername());
String token = accessToken.split(" ")[1];
int expiration = (int) jwtTokenUtils.getExpiration(token);
redisUtil.setBlackList(token, "accessToken", expiration);
}
...
}
원인
deleteRefreshTokenByUsername 메서드 호출 시 EntityManager가 없어서 발생한 에러이다.
JPA/Hibernate OSIV(Open Session In View)는 영속성 컨텍스트를 기본적으로 비즈니스 계층까지 열어둔다. 영속성 컨텍스트는 사용자 요청 시점에서 생성되지만, 데이터를 읽고, 수정할 수 있는 DB 트랜잭션은 비즈니스 계층에서만 사용되도록 트랜잭션이 일어난다. 따라서 해당 메서드를 호출할 때도 Entity Manager는 생성되어 있을 것이라고 추측했다. 그렇다면 해당 에러는 왜 발생했을까?
deleteRefreshTokenByUsername는 쿼리 메소드로 만들었기 때문에 정확한 동작 방식을 알 수는 없지만, 대략적으로 아래와 같은 순서로 동작한다.
- Opening JPA EntityManager
- SELECT 쿼리 실행
- Closing JPA EntityManager
- DELETE 쿼리 → 에러 발생
CrudRepository 인터페이스를 구현하고 있는 SimpleJpaRepository에는 @Transactional이 적용되어 있어서 똑같이 SELECT 쿼리가 나갈 deleteById는 문제없이 동작한다. 하지만 호출한 메서드는 커스텀이다 보니 기본적으로 트랜잭션이 적용되지 않고 조회 쿼리 이후 Entity Manager가 종료되는 것 같다.
해결
핵심은 Entity Manager가 DELETE 쿼리가 실행되기 전 종료되어 발생하는 오류이므로 오래 유지하기 위해 @Transactional을 메소드에 적용했다.
@Transactional
public void logout(String accessToken, Long userId) {
CustomUserDetails user = userRepository.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, NOT_FOUND_USER.name()));
rtRepository.deleteRefreshTokenByUsername(user.getUsername()); //refresh token 삭제
String token = accessToken.split(" ")[1];
int expiration = (int) jwtTokenUtils.getExpiration(token); //access token 만료시간 조회
redisUtil.setBlackList(token, "accessToken", expiration); //redis에 accessToken 사용 못하도록 등록
}
'Java & Kotlin > Spring' 카테고리의 다른 글
Spring Security 6 - Architecture (1) | 2024.06.07 |
---|---|
[TIL / Spring] 설정 파일과 프로필 (0) | 2024.04.26 |
[QueryDSL] SpringBoot 3.1.0, QueryDSL 5.0.0 build.gradle (0) | 2023.08.12 |
[JPA] 지연로딩과 프록시 객체 조회 (0) | 2023.07.21 |
[JPA] 더티체킹과 update (0) | 2023.06.26 |