[Spring Data JPA Tutorial] 2. Spring Data JPA

2021. 2. 18. 19:57Spring/Spring Data JPA Tutorial

반응형
  1. JPA
    1. JPA?
    2. Entity?
    3. EntityManager를 이용한 데이터 조작 방법
  2. Spring Data JPA
    1. 컨셉
    2. Repository 상속 인터페이스의 메서드
    3. Spring Data JPA의 Query methods

1. JPA

1.1. JPA?

Java Persistent API

JPA는 Java ORM 기술에 대한 API 표준 명세입니다.
가장 최근에 배포된 JPA 2.1 사양을 지원하는 Vendor로는 Hibernate, EclipseLink, DataNucleus가 있는데, 그 중 Hibernate를 많이 이용합니다.

ORM(Object Relational Mapping)은 entity 객체(Object)와 database와 매핑하여 SQL 쿼리가 아닌 Java 메서드를 이용하여 데이터를 조작할 수 있게 합니다.
RDBMS의 데이터 read/write를 object를 이용하여 기능할 수 있도록 구현한 것으로, DB의 record하나를 읽는 것이 object하나를 읽는 형태로 구현한 것이라고 보면 됩니다.

DB 레코드를 Java 객체로 표현할 수 있기 때문에, table간의 참조관계를 자바 Object로 표현할 수 있습니다.

Java 객체와 DB 레코드간의 긴밀한 관계가 없던 Mapper 방식은 테이블 컬럼과 자바 객체 프로퍼티를 일일히 매핑해줘야하는 번거로움이 있었습니다.


1.2. Entity?

Entity는 DB에 저장되어있는 데이터를 메모리상의 자바 객체 instance 형태로 매핑한 것입니다.
Entity는 EntityManager라는 인터페이스를 통해 DB와 동기화 됩니다.

웹 애플리케이션에서 JPA를 이용하여 DB 데이터에 접근하기위해서는 반드시 EntityManager를 이용해야 합니다.

@Primary
@Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
    @Qualifier("dataSource") DataSource primaryDataSource,
    @Qualifier("jpaProperties") JpaProperties jpaProperties) {
  return builder
      .dataSource(primaryDataSource)
      .properties(jpaProperties.getProperties())
      .packages("me.jiniworld.demo.models.entities")
      .persistenceUnit("default")
      .build();
}

EntityManager는 EntityMangerFactory bean에 설정된 dataSource를 이용하여 생성되며, EntityMangerFactory bean에는 유니크한 persistenceUnit 이름을 지어줍니다. (위의 코드에서는 persistenceUnit이름을 default라고 설정했습니다.)

@PersistenceContext(unitName = "default")
private EntityManager entityManager;

서비스 class에서 private 필드로 EntityManager를 설정할 때, @PersistenceContext 애너테이션을 이용하여 persistenceUnit을 설정할 수 있습니다.
unitName을 생략할 경우, @Primary 설정된 EntityManagerFactory 빈을 통해 생성된 EntityManager가 매핑됩니다.

이 EntityManager를 이용하여 PersistenceContext(영속성 컨텍스트)의 Entity를 취득할 수 있고, 생성자를 통해 새로 생성한 Entity를 등록할 수 있습니다.

@Transactional 설정된 서비스 메서드의 종료시, PersistenceContext에 축적되어있던 변경정보가 DB에 반영됩니다.
한마디로, PersistenceContext가 DB의 Cache 역할을 한다고 보면 됩니다.

※ 참고로 PersistenceContext는 트랙잭션마다 준비되는 것으로, 같은 트랜잭션에서만 공유됩니다.


1.3. EntityManager를 이용한 데이터 조작

EntityManager를 이용하여 DB에 데이터를 insert하고 select하는 기본 동작을 알아봅시다.

@PersistenceContext
private EntityManager entityManager;

...
Long userId = 11L, cardId = 1000L, storeId = 2L;
Order order = new Order(userId, cardId, storeId);
entityManager.persist(order);   // 저장

entityManager.find(Order.class, 1L);    // 찾기

persist 메서드를 이용하여 user_id=11, card_id=1000, store_id=2인 레코드를 저장하고 (ln 7)
find 메서드로 PK가 1인 order 레코드를 조회합니다. (ln 9)

PK로 지정된 컬럼 이외의 컬럼을 이용하여 조회를 하고 싶다면 JPQL이나 Creteria Query를 이용하여 조회 쿼리를 실행해야합니다.

String query = "SELECT u FROM users u WHERE u.status = :status AND u.type = :type";
String status = "Y", type = "1";
List<User> users = entityManager.createQuery(query, User.class)
  .setParameter("status", status)
  .setParameter("type", type).getResultList();

위의 코드는 JPQL을 이용한 조회 예시 코드입니다.
쿼리 자체가 복잡한 것은 아니나, 위와 같은 간단한 조회쿼리를 작성하기 위해 긴 코드를 작성해야한다는 번거로움이 있습니다.
게다가 query 내에 오타가 있을 경우, 컴파일시에는 오류를 잡아낼 수 없다는 단점도 있습니다.

이런 번거로움을 해소하기 위해 만들어진 것이 Spring Data JPA 입니다.


2. Spring Data JPA

2.1. 컨셉

Spring Data Repository는 Data Access Layer를 구현하는데에 작성되는 상용적인 쿼리 코드를 줄이는 것에 목표를 둔 프로젝트입니다.

단순 쿼리 검색이나 count 검색, 삭제와 저장과 같이 단순한 쿼리 처리를 직접적인 쿼리 작성이 아닌, 약속된 메서드 명명규칙을 따른 Query Methods를 이용하여 처리합니다.

Spring Data Repository 추상화의 맨 중심에는 Repository 인터페이스가 있습니다.

10

흔히들 많이 사용하는 JpaRepository를 상속한 인터페이스들에는 PK를 이용한 단순검색이나 엔티티 인스턴스를 insert, PK를 이용한 record 삭제와 같이 빈번히 이용되는 추상메서드를 미리 선언해둔 interface입니다.

따라서, 이런 약속된 기능을 별도로 선언하고 싶지 않다면 필요에 따라 위의 상속 인터페이스를 이용하면 되고, 사용하고자 하는 메서드를 모두 직접 선언하고 싶다면 Repository 인터페이스를 상속해서 사용해도 무방합니다.

Repository 인터페이스든, Repository를 상속한 CrudRepository나 JpaRepository든 내부적으로는 SimpleJpaRepository 클래스를 이용하고, 이 클래스에서 Query Methods를 분석하여 동적으로 쿼리를 생성합니다.

2.2. Repository 상속 인터페이스의 메서드

Spring Data JPA Repository에 미리 생성되어있는 Repository 상속 인터페이스를 이용하면 빈번히 이용되는 주요 기능들에 대한 Query Methods가 선언되어있습니다.

  • CrudRepository
    • 기본적인 Crud 동작
  • PagingAndSortingRepository
    • CrudRepository 상속 인터페이스
    • 페이징과 정렬
  • JpaRepository
    • PagingAndSortingRepository 상속 인터페이스
    • flush나 deleteInBatch등의 메서드 선언

자세한 사항은 각 인터페이스에 선언된 메서드를 보면 명확히 알 수 있을 것입니다.

public interface CrudRepository<T, ID> extends Repository<T, ID> {

	<S extends T> S save(S entity);
	<S extends T> Iterable<S> saveAll(Iterable<S> entities);
	Optional<T> findById(ID id);
	boolean existsById(ID id);
	Iterable<T> findAll();
	Iterable<T> findAllById(Iterable<ID> ids);
	long count();
	void deleteById(ID id);
	void delete(T entity);
	void deleteAll(Iterable<? extends T> entities);
	void deleteAll();

}
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

	Iterable<T> findAll(Sort sort);
	Page<T> findAll(Pageable pageable);

}
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	void flush();
	<S extends T> S saveAndFlush(S entity);
	void deleteInBatch(Iterable<T> entities);
	void deleteAllInBatch();
	T getOne(ID id);

	@Override List<T> findAll();
	@Override List<T> findAll(Sort sort);
	@Override List<T> findAllById(Iterable<ID> ids);
	@Override <S extends T> List<S> saveAll(Iterable<S> entities);
	@Override <S extends T> List<S> findAll(Example<S> example);
	@Override <S extends T> List<S> findAll(Example<S> example, Sort sort);
}

엔티티에서 위의 메서드들을 사용한다면 위의 인터페이스 중 하나를 상속받은 interface를 새로 생성해도 되고, 또는 위의 메서드들을 참고하여 Query Methods 작성규칙에 맞춰 필요한 메서드를 선언합니다.

Repository 인터페이스를 상속받은 인터페이스에서 Query Methods 작성규칙에 맞춰 메서드를 선언할 경우 해당 메서드를 이용할 때 SimpleJpaRepository 클래스에서 동적으로 쿼리를 생성해줍니다.

2.3. Spring Data JPA의 Query methods

Spring Data JPA는 Spring과 JPA 기반의 repository의 정교한 구축 지원합니다.

org.springframework.data.repository.Repository를 상속한 인터페이스에 정해진 규칙을 따른 Query Methods를 선언할 경우 내부에서 JPQL을 동적으로 생성하여 쿼리를 작동시킵니다.

그렇기에 아래와 같은 JPQL은

@Service
public class UserService {
	...

	public List<User> selectTest() {
		String query = "SELECT u FROM users u WHERE u.status = :status AND u.type = :type";
		String status = "Y", type = "1";
		List<User> users = entityManager.createQuery(query, User.class)
		  .setParameter("status", status)
		  .setParameter("type", type).getResultList();
	}
}

이와 같은 간단한 Query Method로 대신할 수 있습니다.

import org.springframework.data.repository.Repository;

public interface UserRepository extends Repository<User, Long> {
	List<User> findByStatusAndType(String status, String type);
}
@RequiredArgsConstructor
@Service
public class UserService {
	private final UserRepository userRepository;

	public List<User> selectTest() {
		List<User> users = userRepository.findByStatusAndType("Y", "1");
	}
}

이러한 특징에 의해 Spring Data JPA를 활용하여 DB 내의 데이터를 접근할 경우, 단순 입력이나 조회 기능을 위해 쿼리를 일일히 작성해야했던 점을 해소시켜줄 수 있습니다.

단, query가 복잡해질 경우 ORM으로 표현하는 것에는 한계가 존재하고 성능이 raw query에 비해 느려질 수 있습니다.
따라서 복잡한 통계 연산을 구현해야할 때에는 필요에 따라 MyBatis와 함께 병행하여 사용하는 것도 좋은 방법일 수 있습니다.

Query Method는 정해진 키워드를 이용하여 작성해야하는데, 이에 대한 상세한 설명은 다음 포스팅에서 상세히 다루도록 하겠습니다.

728x90
반응형