이번 프로젝트에서는 Spring Data JPA의 기본 CRUD 기능을 넘어 Querydsl, 페이징(Pageable), Auditing, 트랜잭션 전파(Propagation), 롤백 정책 등 JPA의 고급 기능을 학습하였다. 또한 Repository를 인터페이스와 구현체로 분리하여 데이터 접근 계층을 추상화하고, Querydsl을 사용할 수 있는 환경을 구성하는 방법도 익힐 수 있었다.
이번 프로젝트에서는 Querydsl을 사용하기 위해 JPAQueryFactory를 Bean으로 등록하였다.
@Configuration
public class JPAConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory queryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Querydsl은 EntityManager를 기반으로 동작하기 때문에 JPAQueryFactory를 Spring Bean으로 등록하여 필요한 곳에서 주입받아 사용할 수 있도록 구성하였다.
이를 통해 Querydsl을 사용할 수 있는 환경을 설정하는 방법과 EntityManager를 활용하여 타입 안전한 쿼리를 작성하는 구조를 학습하였다.
이번 프로젝트에서는 Repository를 인터페이스와 구현체로 분리하는 구조를 학습하였다.
public interface PhoneRepository {
List<Phone> findAll();
Page<Phone> findAll(Pageable pageable);
void save(Phone phone);
Phone findById(Long id);
}
구현체에서는 Spring Data JPA와 Querydsl을 함께 사용할 수 있도록 구성하였다.
@Repository
@RequiredArgsConstructor
public class PhoneRepositoryImpl implements PhoneRepository {
private final JPAPhoneRepository phoneRepository;
private final JPAQueryFactory queryFactory;
}
Repository 인터페이스는 기능을 정의하고, 구현체에서는 실제 데이터 접근 로직을 수행한다.
이러한 구조를 사용하면 JPA 기본 기능은 JpaRepository를 활용하고, 이후 복잡한 검색 기능은 Querydsl을 이용하여 확장할 수 있어 유지보수가 쉬운 Repository를 설계할 수 있다는 점을 학습하였다.
이번 프로젝트에서는 Spring Data JPA의 Pageable 기능을 이용하여 페이지 단위로 데이터를 조회하는 방법을 학습하였다.
@GetMapping("/list")
public String list(
@PageableDefault(
page = 0,
size = 5,
sort = "id",
direction = Sort.Direction.DESC
)
Pageable pageable,
Model model) {
model.addAttribute(
"phones",
phoneService.findAll(pageable).toList()
);
return "index";
}
Pageable 객체를 이용하면 현재 페이지 번호, 페이지 크기, 정렬 기준 등을 하나의 객체로 전달받을 수 있다.
@PageableDefault를 사용하여 기본 페이지 번호, 한 페이지에 출력할 데이터 개수, 정렬 기준과 방향을 설정하였으며, 이를 통해 대량의 데이터를 효율적으로 조회하는 페이징 기능을 구현하는 방법을 학습하였다.
이번 프로젝트에서는 Entity의 생성일과 수정일을 자동으로 관리하는 Auditing 기능을 학습하였다.
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private Instant createdAt;
@LastModifiedDate
private Instant updatedAt;
}
이를 상속받은 Entity에서는 별도의 코드 없이 생성일과 수정일을 사용할 수 있었다.
public class Phone extends BaseEntity {
@CreatedDate는 엔티티가 처음 저장될 때 생성 시간을 자동으로 저장하고, @LastModifiedDate는 수정될 때마다 시간을 자동으로 갱신한다.
이를 통해 서비스 계층에서 시간을 직접 저장하지 않아도 JPA가 생성일과 수정일을 관리해 주는 기능을 학습하였다.
데이터 수정은 더티 체킹을 이용하여 처리하였다.
@Transactional
public void changeName(Long id, String name) {
Phone phone = findById(id);
phone.changeName(name);
}
트랜잭션 안에서 조회한 Entity는 영속성 컨텍스트에 의해 관리된다.
조회한 객체의 값을 변경하면 트랜잭션 종료 시점에 변경 사항을 자동으로 감지하여 UPDATE SQL을 실행한다.
이를 통해 별도의 update 메서드를 작성하지 않고도 객체의 상태 변경만으로 데이터가 수정되는 더티 체킹의 동작 원리를 이해할 수 있었다.
이번 프로젝트에서는 트랜잭션 전파 속성도 학습하였다.
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void tx2() {
save(Phone.builder()
.name("REQUIRES_NEW")
.build());
}
Propagation.REQUIRES_NEW는 기존 트랜잭션과 별개의 새로운 트랜잭션을 생성한다.
외부 트랜잭션에서 예외가 발생하더라도 새로운 트랜잭션은 독립적으로 Commit 또는 Rollback을 수행할 수 있다는 점을 학습하였다.
이를 통해 하나의 서비스 안에서도 여러 트랜잭션을 독립적으로 관리할 수 있는 방법을 이해할 수 있었다.
프로젝트에서는 Self Invocation 현상도 확인하였다.
@Transactional
public void tx2Out() {
tx2();
System.out.println(1 / 0);
}
반면 다른 Service를 통해 호출하면
@Transactional
public void tx2Out() {
phoneService.tx2();
System.out.println(1 / 0);
}
Spring의 @Transactional은 프록시 기반 AOP로 동작하기 때문에 같은 클래스 내부에서 자신의 메서드를 호출하는 경우에는 프록시를 거치지 않는다.
반대로 다른 Service를 통해 호출하면 프록시가 적용되어 트랜잭션 전파가 정상적으로 동작한다는 점을 학습하였다.
이번 프로젝트에서는 예외 종류에 따라 Rollback 여부를 설정하는 방법도 학습하였다.
@Transactional(
rollbackFor = {
NullPointerException.class,
NoSuchElementException.class
},
noRollbackFor = ArithmeticException.class
)
rollbackFor를 사용하면 지정한 예외가 발생했을 때 트랜잭션을 롤백할 수 있고, noRollbackFor를 사용하면 특정 예외가 발생하더라도 롤백하지 않도록 설정할 수 있다.
이를 통해 서비스의 특성에 맞게 트랜잭션 정책을 세밀하게 제어하는 방법을 학습하였다.
| 학습 내용 | 배운 내용 |
|---|---|
| Querydsl | JPAQueryFactory를 이용한 Querydsl 환경 구성 |
| Repository 구조 | 인터페이스와 구현체를 분리하여 데이터 접근 계층 설계 |
| Pageable | 페이지 번호, 크기, 정렬을 이용한 페이징 처리 |
| Auditing | 생성일과 수정일 자동 관리 |
| 더티 체킹 | Entity 상태 변경만으로 UPDATE 수행 |
| 트랜잭션 전파 | Propagation.REQUIRES_NEW를 이용한 독립적인 트랜잭션 관리 |
| Self Invocation | 프록시 기반 AOP와 자기 자신 호출의 차이 |
| Rollback 정책 | rollbackFor, noRollbackFor를 이용한 예외별 트랜잭션 제어 |
이번 프로젝트를 통해 JPA의 기본 CRUD 기능을 넘어 실무에서 자주 사용하는 고급 기능들을 직접 구현하고 동작 원리를 이해할 수 있었다. 특히 Querydsl 환경 구성과 Repository 추상화, Auditing, Pageable, 더티 체킹, 트랜잭션 전파, Rollback 정책 등을 적용해 보면서 JPA가 단순한 ORM 기술이 아니라 객체 중심의 데이터 관리와 트랜잭션 제어를 지원하는 프레임워크라는 점을 이해할 수 있었다. 또한 프록시 기반 AOP와 Self Invocation 문제를 실습하면서 Spring의 트랜잭션이 내부적으로 어떻게 동작하는지에 대해서도 학습할 수 있었다.