https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
Spring Data Jpa와 Jpa는 이전에 한번 학습했으므로 가볍게 중요 내용을 요약하겠으며, Querydsl을 좀 더 중점적으로 다루겠습니다.
물론 이번 스프링 DB 2편에서는 가볍게 경험하는 정도이기에 나중에 더 자세한 포스팅을 올리겠습니다!
JPA
- implementation 'org.springframework.boot:spring-boot-starter-data-jpa'을 build.gradle에 추가한다.
이러면 Spring Data Jpa와 Jpa 설정이 완료된다. - spring-boot-starter-data-jpa 는 spring-boot-starter-jdbc 도 함께 포함(의존)한다.
따라서 해당 라이브러리 의존관계를 제거해도 된다. - Jpa는 public 또는 protected 기본 생성자가 필수다. 동적 프록시 기술 때문인 것 같다.
- Repository를 작성했는데 생성자를 보면 스프링을통해 엔티티 매니저가 주입받는 것을 알 수 있다.
JPA의 모든 동작은 엔티티 매니저를 통해 이루어지며, 엔티티 매니저는 내부에 데이터소스를 가지고 있고, DB에 접근할 수 있다. - JPA의 모든 데이터 변경(등록, 수정, 삭제)은 트랜잭션 안에서 이루어져야 한다. 조회는 트랜잭션이 없어도 가능하다.
- JPA를 설정하려면 EntityManagerFactory , JPA 트랜잭션 매니저( JpaTransactionManager ), 데이터소스 등등
다양한 설정을 해야 한다. 스프링 부트는 이 과정을 모두 자동화 해준다. 스프링 빈으로 모두 등록해준다.
결국 엔티티 매니저도 자동으로 스프링 빈으로 등록된다. - JPA는 JPQL이라는 객체지향 쿼리 언어를 사용한다.
예외 변환
- JPA의 경우에는 예외가 발생하면 JPA 에러가 발생한다.
- EntityManager는 순수한 JPA 기술이고, 스프링과는 관계가 없다.
따라서 엔티티 매니저는 예외가 발생하면 JPA 예외를 발생시킨다. - 그렇다면 JPA 예외를 스프링 예외 추상화로 어떻게 변환할 수 있을까?
- @Repository를 사용하면 가능하다.
@Repository의 기능
- @Repository 가 붙은 클래스는 컴포넌트 스캔의 대상이 된다.
- @Repository 가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.
- 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기 ( PersistenceExceptionTranslator )를 등록한다.
- 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이터 접근 예외로 변환한다.
- 결과적으로 리포지토리에 @Repository 애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.
Spring Data Jpa
- 동적 프록시 기능을 사용해 스프링 데이터 JPA가 인터페이스만 작성해도 구현체가 자동으로 스프링 빈으로 등록된다.
별도로 개발자가 @Component, @Repository를 작성할 필요가 없다. - 가장 대표적인 기능은 공통 인터페이스 기능과 쿼리 메서드 기능이다.
- 쿼리 메서드 기능 대신에 직접 JPQL을 사용하고 싶을 때는 @Query 와 함께 JPQL을 작성하면 된다.
이때는 메서드 이름으로 실행하는 규칙은 무시된다. - 스프링 데이터 JPA는 JPQL 뿐만 아니라 JPA의 네이티브 쿼리 기능도 지원하는데, JPQL 대신에 SQL을 직접 작성할 수 있다.
- 역시 동적 쿼리에 약하다.
- 실습은 기존에 인터페이스를 사용하기 위해 어댑터 패턴을 사용했다.
- 결론은 JPA 기초를 잘 알아야 Spring Data JPA도 잘 쓸 수 있다!
Querydsl
Querydsl 설정
dependencies {
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}
- 위는 Querydsl 설정을 위해 build.gradle에 작성한 코드다.
- Querydsl은 인텔리제이가 버전업하거나, Gradle 설정이 버전업하면서 적용 방법이 조금씩 달라진다고 한다.
- 그래서 설정에 수고로움이 많다고 하는데, querydsl gradle로 검색해서 그때그때 대안을 찾아야한다고 한다.
- 빌드 툴이 IntelliJ 일때랑 gradle일 때 Q타입 생성 확인 방법이 다르다.
메서드 예시 코드
@Repository
@Transactional
public class JpaAndQuerydslItemRepository implements ItemRepository {
private final EntityManager em;
private final JPAQueryFactory query;
public JpaAndQuerydslItemRepository(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
//생략
@Override
public List<Item> findAll(ItemSearchCond itemSearch) {
String itemName = itemSearch.getItemName();
Integer maxPrice = itemSearch.getMaxPrice();
List<Item> result = query
.select(item)
.from(item)
.where(likeItemName(itemName), maxPrice(maxPrice)) //and 조건으로 인식됨.
.fetch();
return result;
}
private BooleanExpression likeItemName(String itemName){
if(StringUtils.hasText(itemName)){
return item.itemName.like("%" + itemName + "%");
}
return null;
}
private BooleanExpression maxPrice(Integer maxPrice){
if (maxPrice != null) {
return item.price.loe(maxPrice);
}
return null;
}
}
- Querydsl을 사용하려면 JPAQueryFactory 가 필요하다.
- JPAQueryFactory 는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager 가 필요하다.
- 참고로 JPAQueryFactory 를 스프링 빈으로 등록해서 사용해도 된다.
- Querydsl 은 별도의 스프링 예외 추상화를 지원하지 않는다. 대신에 JPA에서 학습한 것 처럼 @Repository 에서 스프링 예외 추상화를 처리해준다.
- 동적 쿼리 작성이 매우 간편해졌다.
- Querydsl은 결과적으로 JPQL 빌더다. JPQL에 대해 잘 알아야 한다.
데이터 접근 기술 활용 방안
- 좋은 이야기를 많이 들을 수 있었던 파트다.
- 어댑터 구조(DI, OCP) VS 동적 프록시로 만들어진 레포지토리에 의존인가에 대한 고민이 있었다.
- 어댑터 구조는 코드를 설계적으로는 좋지만 코드가 복잡하고, 프록시 객체에 의존은 코드도 간편하고 빠른 개발이 가능하지만 설계적으로 별로다.
- 여러 오픈 커뮤니티에서도 다양한 의견을 봤는데, 본인은 일단 빠르게 개발하고 필요하다면 리팩토링을 하는 것이 좀 더 나은 방안이라고 생각한다.
- 마지막 실습은 Spring Data JPA와 Querydsl을 같이 사용했다. 각각의 레포지토리를 만들고 개발을 했다.
JpaTransactionManager의 지원
- JpaTransactionManager는 DataSourceTransactionManager 가 제공하는 기능도 대부분 제공한다.
- JPA라는 기술도 결국 내부에서는 DataSource와 JDBC 커넥션을 사용하기 때문이다.
- 따라서 JdbcTemplate , MyBatis 와 함께 사용할 수 있다.
- 결과적으로 JpaTransactionManager 를 하나만 스프링 빈에 등록하면, JPA, JdbcTemplate, MyBatis 모두를
하나의 트랜잭션으로 묶어서 사용할 수 있다. - 물론 함께 롤백도 할 수 있다.
- 나중에 JdbcTemplate와 JPA를 같이 쓰면 플러시라는 기능을 잘 사용해야 한다.
필자가 느끼기에는 플러시는 커밋과 비슷한 것 같다.
마지막 실습은 필자가 별도로 진행한 실습이다.
스프링 부트가 자동으로 편리하게 많은 빈들을 등록해준다.
우리 애플리케이션이 뜰 때 어떤 빈들이 있는지 궁금해서 아래와 같은 테스트 코드를 작성했다.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class ItemServiceApplicationTests {
@Autowired
DefaultListableBeanFactory beanFactory;
@Test
@DisplayName("모든 빈 출력하기")
void findAllBean() {
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
Object bean = beanFactory.getBean(beanDefinitionName);
System.out.println("name=" + beanDefinitionName + " object=" + bean);
}
}
@Test
@DisplayName("애플리케이션 빈 출력하기") void findApplicationBean() {
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
//Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = beanFactory.getBean(beanDefinitionName);
System.out.println("name=" + beanDefinitionName + " object=" + bean);
}
}
}
}
정말 수 많은 스프링 빈들이 출력됐다.
그동안 배운 트랜잭션 매니저, 데이터소스, 우리가 등록한 서비스와 레포지토리, 엔티티 매니저 팩토리 등이 있었다.
그런데 엔티티 매니저는 왜 안보이는 걸까? 분명 빈으로 등록되야 스프링이 의존 관계를 주입해줄텐데..
EntityManagerCreator, EntityManagerFactory만 보인다.
일단 보이는 LocalContainerEntityManagerFactoryBean이 EntityMangerFactory를 생성하는 클래스라고 한다.
그러면 EntityMangerFactory는 요청이 들어올 때마다 엔티티 매니저를 생성한다고 하는데 이 때 엔티티 매니저를 주입해주는 것일까?
아마 엔티티 팩토리가 무슨 마법을 부리는 것이 아닐까??
뭔가 의문이 늘었다..이에 관해서는 나중에 질문이나 자료를 찾아봐서 정리해봐야겠다.
작성하고 10분 계속 자료를 찾는 중 일단 아래와 같은 질문을 발견했다.
프록시로 가짜 엔티티 매니저를 주입 받는다. 이러면 의문점이 해소되었다.
https://www.inflearn.com/questions/262952
https://www.inflearn.com/questions/387369
이상으로 포스팅을 마칩니다. 감사합니다.
댓글