도전과제 다했다.
커밋을 좀 꼼꼼히 (?) 해보려고 했더니 41개나 되었다. readme는 당일날 천천히 추가해야겠다.
https://github.com/roqkfchqh/Calendar
GitHub - roqkfchqh/Calendar
Contribute to roqkfchqh/Calendar development by creating an account on GitHub.
github.com
persistence스러운 기능들을 써봤다.
고집과 지속성에는 어떤 관계가 있을까? 고집이 세서 상태가 지속된다 보면 되려나
1. 영속성 컨텍스트(Persistence Context)
영속성 컨텍스트는 jpa의 핵심 개념으로, 엔티티를 관리하고 db와의 상태를 동기화하는 역할을 한다. 쉽게 말해서 jpa가 엔티티를 관리하는 가상의 작업공간이다.
엔티티 객체는 jpa에서 3가지 상태 중 하나에 속한다.
비영속 상태: 영속성 컨텍스트와 관계없는 상태. jpa의 persist()를 호출하지 않은 상태다.
영속 상태: 영속성 컨텍스트에 의해 관리되는 상태. 엔티티 매니저가 db와의 동기화를 책임진다. persist()를 호출하면 비영속에서 영속으로 전환된다.
준영속 상태: 영속성 컨텍스트에서 분리된 상태. db와의 동기화가 중단된 상태다. detach(), clear(), close()를 호출하면 발생한다.
영속성 컨텍스트의 핵심 기능은 크게 4가지가 있다.
1차 캐시: 동일한 트랜잭션 내에서, 동일한 엔티티를 조회하면 db에 접근하지 않고 캐시된 엔티티를 반환한다. 그로인해 성능이 최적화되고, 데이터의 일관성을 보장한다.
변경 감지: 영속 상태의 엔티티를 지속적으로 추적한다. 트랜잭션이 종료되기 전에 엔티티의 변경 사항을 감지하여, db에 자동으로 반영한다. jpa는 엔티티의 스냅샷(초기 상태)을 유지하고, 변경사항이 발생하면 이를 비교해 update쿼리를 실행한다.
쓰기 지연: 트랜잭션이 커밋되기 전까지 변경된 데이터(insert, update, delete)는 sql로 즉시 실행되지 않고 쓰기 지연 sql 저장소에 저장된다. commit()시점에 한번에 실행돼 성능을 최적화한다.
지연 로딩: 연관된 엔티티를 실제로 사용할 때 db에서 가져오는 방식이다. 영속성 컨텍스트를 통해 필요 데이터를 동적으로 로드한다. 준영속 상태에서는 지연 로딩이 불가능하며, exception이 발생할 수 있다.
주의사항
트랜잭션: 영속성 컨텍스트는 트랜잭션 범위 내에서만 유지된다. 트랜잭션이 종료되면 영속성 컨텍스트가 닫히고, 엔티티는 준영속 상태로 전환된다.
메모리 관리: 너무 많은 엔티티를 영속성 컨텍스트에 보관하면 메모리 문제가 발생할 수 있으므로 주기적으로 clear()를 호출하여 필요없는 엔티티를 제거해야 한다.
내 코드에 실제 사용한 예시)
validateUser : 세션에서 user를 검증하고 repository를 호출하며 User를 반환해주는 메서드이다.
1. 영속성 컨텍스트 (트랜잭션) 적용 전
public UserResponseDto updateUser(
UpdateRequestDto dto,
HttpServletRequest req){
User user = userValidationService.validateUser(req);
userValidationService.validatePassword(user.getPassword(), dto.getCurrentPassword());
user.updateUser(dto.getName(), dto.getNewPassword());
userRepository.save(user)
return UserMapper.toDto(updatedUser);
}
userRepository에 save를 해줘야한다.
2. 영속성 컨텍스트 적용 후
@Transactional
public UserResponseDto updateUser(
UpdateRequestDto dto,
HttpServletRequest req){
User user = userValidationService.validateUser(req);
userValidationService.validatePassword(user.getPassword(), dto.getCurrentPassword());
user.updateUser(dto.getName(), dto.getNewPassword());
return UserMapper.toDto(user);
}
@Transactional 어노테이션을 사용하고, validateUser에서 레포지토리를 호출하며 user이 영속성 컨텍스트로 지정되었다.
save를 하지않아도 dirtycheck 시 이상이 없으면 update가 반영된다.
2. JPQL(Java Persistence Query Language)
jpa의 쿼리 언어로, sql과 유사하지만 엔티티 객체를 대상으로 쿼리를 작성한다. 작성 시에도 테이블이 아닌 엔티티 이름과 필드에 기반해야 한다.
테이블 대신 엔티티 객체를 대상으로 작성하므로 객체 지향적이며, 특정 db에 종속되지 않으므로 독립적이라 볼 수 있다.
또한 영속성 컨텍스트와의 동작이 자연스럽게 통합된다. fetch join을 활용해 지연 로딩 문제도 해결 가능하다.
단점은 복잡한 쿼리의 작성이 제한되고(이럴 땐 네이티브 sql을 사용해야 한다) 특정 db기능을 활용한 성능 최적화엔 한계가 있으므로 queryDSL도 고려해야 한다.
내 코드 jpql 사용 예시
@Query("SELECT new com.calendar.dto.response.CommentResponseDto(" +
"c.id, c.content, c.user.name, c.created, c.updated) " +
"FROM Comment c WHERE c.calendar = :calendar")
Page<CommentResponseDto> findCommentDtoByCalendar(@Param("calendar") Calendar calendar, Pageable pageable);
@Query("SELECT new com.calendar.dto.response.CalendarResponseDto(" +
"c.id, c.title, c.content, c.user.name, COUNT(com), c.created, c.updated) " +
"FROM Calendar c LEFT JOIN Comment com ON com.calendar = c " +
"GROUP BY c.id, c.title, c.content, c.user.name, c.created, c.updated " +
"ORDER BY c.updated DESC")
Page<CalendarResponseDto> findAllWithCommentNum(Pageable pageable);
jpql로 작성해 responseDto로 데이터를 직접 조회하였다. new 키워드를 통해 jpql에서 dto로 데이터를 매핑하면 된다.
다음엔 queryDSL을 사용해봐야겠다.
'개인 공부용 프로젝트 > calendar.jpa.api' 카테고리의 다른 글
트러블슈팅 : 필수과제 (0) | 2024.12.11 |
---|