Notice
Recent Posts
Link
Tags
- PYTHON
- 스프링부트
- mysql
- spring boot
- AWS
- nginx
- Django
- jpa
- spring
- 스프링
- 자바
- DI
- string
- java
- ORM
- springboot
- SSL
- spring security 6
- spring mvc
- 데이터베이스
- session
- sql
- 문자열
- Git
- select
- Docker
- 1차원 배열
- @transactional
- join
- 프로그래머스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Archives
개발하는 자몽
[Spring Data JPA] TransactionRequiredException 본문
아래 블로그 글을 참고하여 정리했습니다.
상황
에러 문구
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 > 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 |
Comments