JPA
- 자바에서 orm기술 표준으로 사용하는 인터페이스
orm
- 자바 객체와 관계형 db의 데이터를 자동으로 매핑해주는 방법
Hibernate
- 자바 언어를 위한 orm 프레임워크
- JPA 인터페이스를 구현하고 내부적으로 JDBC API를 사용
Spring Data JPA
- jpa를 더 쉽고 편하게 사용할 수 있게 도와주는 spring에서 제공되는 모듈
- jpa를 한 단계 더 추상화 시킨 jparepository 인터페이스 제공
- entitymaniger는 jpa에서 관리하는 것
- @transactional은 스프링에서 관리하는 것

Raw JPA

Persistence Context

- 엔티티 객체가 생성, 관리, 소멸 되기까지 데이터베이스와 상호작용하는 모든 과정
- 엔티티를 JPA 영속성 컨텍스트에 영속화(persist) 하게 되면, 이후 JPA 영속성 컨텍스트에서 상태를 관리함
Entity Manager Factory
- jpa에서 엔티티 메니저를 생성하고 관리하는 팩토리 객체
- 애플리케이션 실행시 한번만 생성됨, 애플리케이션 전체에서 공유됨.
- 여러 스트레드가 동시에 객체나 메서드에 접근할 때 데이터의 일관성을 보관할 수 있음
- 엔티티 메시저 생성, 디비 연결, 트랜젝션 관리
Entity Manager
- 디비와 상호작용하는 객체, 엔티티의 생명 주기를 관리, 여러 스레드가 동시에 접근할 때 일관성 보장 ㄴㄴ
- 엔티티 객체의 라이프 사이클과 영속성 관리
- 엔티티 객체에 대해서 디비 crud 연산 수행

OSIV(Open Session in View)
영속성 컨텍스트를 View 렌더링이 끝날 때까지 개방된 상태로 유지하는 방식
장점
- 컨트롤러나 뷰에서도 지연 로딩 사용 가능
단점 : 디비 커넥션 점유
- 컨트롤러와 뷰 렌더링이 끝날 때까지 디비 커넥션 유지
- 커넥션 보유 시간이 길어져 서버 자원 많이 사용
- 과도한 db 커넥션 사용으로 인한 성능 저하 가능성
OSIV를 비활성화(false) 하여 사용하는 것이 좋음
spring.jpa.open-in-view=false

Proxy

- 프록시(Proxy): 실제 엔터티 객체 대신, 그 객체를 감싸고 있는 대리 객체
- 실제 데이터는 데이터베이스에서 가져오지 않고, 필요한 시점에만 쿼리를 실행해 데이터를 조회하는 방식
- 불필요한 쿼리의 발생을 방지

이게 바로 프록시 객체!
무비를 조회할 때 연관관계가 맺어진 감독들 객체를 lazy로 조회
감독들을 제외하고 조회하는 경우에 무비 안에 있는 배우는 프록시로 조회됨
-> 불필요한 DB 통신 미사용 및 DB 부하 감소
@Transactional을 사용할 때 private 접근제어자를 사용하면 안 됨
- @transactional은 aop로 만들어져 있음
- aop도 내부적으로 프록시를 사용하여 만들어져 있음
- 프록시가 동작을 하려면 내부적으로 상속이든 구현체든 뭐가 되어서 접근을 해야하는데, 메서드가 private으로 되어 있으면 접근 못함

@Transactional
- Spring에서 메서드 또는 클래스에 적용하여 트랜잭션을 선언적으로 관리하는 방법
- 트랜잭션 범위에 대해 Proxy 패턴을 사용하여 트랜잭션 관리
- 설정 가능한 옵션 제공
- 종류 : propagation(기본), isolation, timeout, readOnly, rollbackFor, noRollbackFor
- readOnly = true로 설정하여 성능상 이점을 가질 수 있음 -> 변경 감지(Dirty Checking)가 비활성화
예시 : rollbackFor - 해당 예외가 발생할 때만 롤백을 수행한다
@Transactional(rollbackFor = {IOException.class, SQLException.class})
public void registerUser(User user) throws IOException, SQLException {
userRepository.save(user);
if (someCondition) {
throw new IOException("IOException 발생!"); // 롤백
}
if (someOtherCondition) {
throw new SQLException("SQLException 발생!"); // 롤백
}
}
Propagation

- 트랜잭션이 다른 트랜잭션과 어떻게 상호작용할지를 결정하는 방식
- 서로 다른 서비스에 있는 트랜잭션끼리 어떻게 관리되고 전파할것인지 결정하는 것
- 종류
- REQUIRED (기본값): 이미 진행 중인 트랜잭션이 있으면 그 트랜잭션을 사용하고, 없으면 새 트랜잭션을 시작
- REQUIRES_NEW: 항상 새 트랜잭션을 시작하며, 진행 중인 트랜잭션은 잠시 중단
- SUPPORTS: 트랜잭션이 이미 존재하면 그 트랜잭션 내에서 실행하고, 없으면 비트랜잭션 실행
- MANDATORY: 현재 트랜잭션이 반드시 있어야 하며, 없으면 예외가 발생
- NEVER: 트랜잭션 없이 실행되어야 하며, 트랜잭션이 존재하면 예외가 발생
- NESTED: 진행 중인 트랜잭션 내부에 또 다른 트랜잭션을 중첩시켜 실행할 수 있으며, 이 경우 Savepoint를 이용
Propagation 예시 (REQUIRES_NEW)
현재 서로 다른 서비스에서 개별적으로 트랜잭션이 관리되고 있음
만약 changeUserRole에서 에러가 발생하면 사용자는 역할을 변경하지 못하고 로그도 찍히지 못할 것이다
기본값이 REQUIRED이기 때문에 saveLog와 changeUserRole은 같은 트랜잭션을 쓰기 때문이다!
@Transactional
public void saveLog(Long userId, LocalDateTime requestTime) {
Log log = new Log(userId, requestTime);
logRepository.save(log);
}
@Transactional
public void changeUserRole(AuthUser authUser, long userId, UserRoleChangeRequest userRoleChangeRequest) {
User requestUser = User.fromAuthUser(authUser);
logService.saveLog(requestUser.getId(), LocalDateTime.now());
User user = userRepository.findById(userId).orElseThrow(() -> new InvalidRequestException("User not found"));
user.updateRole(UserRole.of(userRoleChangeRequest.getRole()));
}
이때 changeUserRole에서 에러가 발생해도 saveLog는 그와 상관없이 독립적으로 수행되고 싶다면 Propagation 설정을 REQUIRES_NEW로 변경해주면 된다!
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Long userId, LocalDateTime requestTime) {
}
'Spring' 카테고리의 다른 글
failed to create jar file 문제 해결하기 (1) | 2025.03.25 |
---|---|
테스트 코드 작성하기 (1) | 2025.03.23 |
Spring Security의 JWT 적용 (1) | 2025.03.21 |
User 데이터 100만건 생성하고 성능 개선하기 (1) | 2025.03.21 |
intellij 프로젝트 내부에 작업물이 안 보일 때 해결방법 (0) | 2025.03.12 |