본문 바로가기
프로그래밍/JPA

[JPA] 스프링 데이터 JPA

by yonmoyonmo 2021. 5. 29.
김영한님의 책 [JPA 프로그래밍]을 읽고 요약한 내용입니다.

스프링 데이터 JPA

스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트이다. 원래라면 일일히 구현해야 했던 데이터 접근 계층에서(repository) 각 엔티티의 기본적인 CRUD를 공통 인터페이스로 한 방에 처리하여 귀찮음을 엄청나게 덜어 준다.

public interface MemberRepository extends JpaRepository<Member, Long> {
}

이렇게 엔티티 타입과 @Id로 지정한 필드의 타입을 제네릭에 넣어서 JpaRepository를 상속하는 인터페이스만 만들어도 기본적인 Member에 대한 데이터베이스 CRUD를 쓸 수 있게 된다.

주요 공통 인터페이스 메소드

  • save(Entity) : 새로운 엔티티 저장, 혹은 수정
  • delete(Entity) : 삭제
  • findOne(ID) : 하나 찾기
  • getOne(ID) : 엔티티를 프록시로 조회한다.(em.getReference())
  • findAll(정렬이나 페이징 조건) : 다 찾는다.

 

쿼리 메소드 기능

메소드 이름으로 쿼리 생성

기본적으로 제공하는 CRUD 외에, findByEmail() 같이 직접 구현해야 할 것 같은 부분도 리턴 타입, 파라미터, 메소드 이름만 적어주면 알아서 분석해서 목표로 한 기능을 하는 JPQL을 알아서 만들어 준다. 다만 메소드 이름을 짓는 데에는 규칙이 있으니 알아 두어야 한다. 

**엔티티의 필드 이름이 변경되면 인터페이스에 정의한 메소드 이름도 함께 변경해야한다.

public interface MemberRepository extends JpaRepository<Member, Long> {
	Member findByEmail(String Email); //이러면 이제 이 메소드를 쓸 수 있음
}

레퍼런스 : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.declared-queries

JPA NamedQuery

쿼리에 이름을 부여해서 불러다가 쓸 수 있는 기능이다.

엔티티를 코딩할 때 @NamedQuery 어노테이션을 써서 같이 만들어 놓고 불러다 쓸 수 있다.

@Entity
@NamedQuery(
	name = "Member.findByUsername",
	query="select m from Member m where m.username = :username")
public class Member {
	....
}
요래 해서
public interface MemberRepository extands JpaRepository<Member, Long> {
	List<Member> findByUsername(@Param("username") String username);
}
요래 할 수 있다.

@Query, 리포지토리 메소드에 쿼리 정의

public interface MemberRepository extands JpaRepository<Member, Long> {
	@Query("select m from Member m where m.username = :username")
	Member findByUsername(@Param("username") String username);
}

요런식으로 정의해서 써도 된다고 한다.

벌크성 수정 쿼리

@Modifying 어노테이션을 사용한다. 

@Modifying
@Query("update Product p set p.price = p.price * 1.1 where p.stock < : stock")
int bulkPriceUp(@Param("stock") String stock);

요래 쓰면 된다.

벌크연산 후 영속성 컨텍스트를 초기화하고 싶으면 @Modifying(clearAutomatically = true) 하면 된다.

사용자 정의 리포지토리 구현

스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는다.

그러나 가끔 메소드를 직접 구현해야 할 때에는 어떻게 해야할까?

공통 인터페이스가 제공하는 기능을 사용하면서 직접 필요한 메소드를 구현하는 방법은 이렇다.

먼저 직접 구현할 메소드를 위해 인터페이스를 하나 작성한다. 이 인터페이스 이름은 자유롭게 지을 수 있다.

public interface MemberRepositoryCustom {
	public List<Member> findMemberCustom();
}

이 인터페이스를 구현한 구현체를 작성한다. 이 구현체는 인터페이스 이름 + Impl 로 지어야 한다. 이러면 스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식한다.

public class MemberRepositoryImpl implements MemberRepositoryCustom {
	@Override
	public List<Member> findMemberCustom() {
		...여기다가 커스텀 메소드를 구현...
	}
}
그리고 리포지토리 인터페이스에서 아래처럼 상속하면 된다.

public interface MemberRepository extends Jparepository<Member, Long>, MemberRepositoryCustom {
}

 

트랜잭션 범위의 영속성 컨텍스트

스프링 환경에서 JPA를 사용하면 컨테이너가 트랜잭션과 영속성 컨텍스트를 관리해준다. 컨테이너 환경에서 동작하는 JPA 내부 동작 방식을 알고 써야 좋다.

스프링 컨테이너의 기본 전략

스프링 컨테이너는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다. 트랜잭션 범위와 영속성 컨텍스트의 생존 범위가 같다. 트랜잭션을 시작할 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날 때 영속성 컨텍스트를 종료한다. 같은 트랜잭션 안에서는 항상 같은 영속성 컨텍스트에 접근한다. 

스프링 컨테이너는 쓰레드마다 각각 다른 트랜잭션을 할당한다. 같은 엔티티 매니저를 사용해도 트랜잭션이 다르다면 영속성 컨텍스트가 달라진다. 그러므로 멀티 쓰레드를 신경 안쓰고 비지니스 로직에 집중해서 개발할 수 있다.

FACADE 계층

영속성 컨텍스트가 프레젠테이션 계층에서는 준영속 상태에 있게 된다. 프레젠테이션 계층에서 지연 로딩을 발생 시킬 경우 영속상태가 아니기 때문에 예외가 발생한다. 이 경우 미리 프레젠테이션 계층에서 필요한 엔티티를 미리 프록시 초기화 해서 넘기는 방법과 OSIV를 사용해서 엔티티를 항상 영속상태로 유지하는 방법 두 개의 해결법이 있다.

미리 필요한 엔티티 로딩하는 방법

글로벌 하게 즉시 로딩만 사용하게 하는 방법이나 데이터 접근 계층에서 프레젠테이션 계층으로 엔티티를 넘기기 전에 강제로 getter를 써서 프록시를 초기화한 후에 넘기는 방법 등이 있다.

전부 즉시 로딩만 사용하게 하는 방법은 어플리케이션 전체에 비효율을 늘리게 된다. JPQL fetch join 으로 어느정도 비효율을 줄일 수 있긴 하지만 프레젠테이션 계층에 너무 종속된 메소드가 점점 늘 수 도 있다.

엔티티를 넘기기 전에 초기화 하는 방법이 괜찮은 것 같다.

이 방법의 단점은 서비스 계층에 프록시 초기화를 위한 코드가 계속 끼게 되는데 이 코드를 분리하여 하나의 계층으로 설정한 것이 FACADE 계층이다.

*facade(파사드) : 대충 건물 정면이라는 뜻

파사드 계층의 역할과 특징 : 

  • 프레젠테이션 계층과 도메인 모델 계층 간의 논리적 의존성을 분리해준다.
  • 프레젠테이션 계층에서 필요한 프록시 객체를 초기화한다.
  • 서비스 계층을 호출해서 비즈니스 로직을 실행한다.
  • 리포지토리를 직접 호출해서 프레젠테이션 계층에서 요구하는 엔티티를 찾는다.

요런 식으로 프록시 초기화 하는 부분을 파사드 클래스에서 해주도록 작성하고 프레젠테이션 계층에서는 파사드 계층에서 데이터를 얻는다.

단점은 굉장히 번거롭고 코드도 많아지고 계층도 추가되고 프레젠테이션 계층에서 초기화여부를 알아보기 위해 파사드를 계속 열어봐야 한다는 점이다. 

class OrderFacade {
	@Autowired OrderService orderService;
	public Order findOrder(Long id) {
		Order order = orderService.findOrder(id);
		order.getMember().getName(); //프록시 초기화
		return order;
	}
}

class OrderService {
	public Order findOrder(Long id) {
		return orderRepository.findOrder(id);
	}
}

OSIV

Open Session In View

영속성 컨텍스트를 뷰까지 열어둔다는 뜻이다. 이러면 프레젠테이션 계층에서도 지연로딩을 문제 없이 할 수 있다.

하지만 트랜잭션이 프레젠테이션 계층에서부터 열려있게 된다는 뜻인데 딱 봐도 별로다. 사실 요즘은 프레젠테이션 계층에서는 읽기 전용으로만 영속성 컨텍스트를 열어 두고 트랜잭션은 데이터 접근 계층에서만 관리하도록 하는 것이 보통의 OSIV라고 한다. 

Request가 들어오면 영속성 컨텍스트가 생성되고 쭈욱 살아 있게 하고 트랜잭션은 원래 있어야 할 곳에만 있도록 할 수 있다고 한다.

어떻게 하는 지는 구글링 ㄱㄱ

이 글을 마지막으로 JPA 포스팅 끝...입니..다...

여태까지의 포스팅은 1장 - 13장까지의 내용입니다.

14장 - 16장은 고급 주제입니다. 포스팅 하기 너무 귀찮네요!

이 책을 구입하셔서 보시는 것을 추천드립니다!!

김영한의 JPA 프로그래밍 추천!!

 

'메모장/JPA' 카테고리의 글 목록

프로그래밍 & 일상

wonmocyberschool.tistory.com

 

댓글