개발/Spring DB

JPA 관련 기술 파트 요약 및 의문점

Debin 2022. 6. 23.
반응형
본 게시글은 인프런 김영한 선생님 강의 스프링 DB 2편을 완강하고 배운 것을 남기고자 적은 포스팅입니다.
강의 링크는 아래와 같습니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard

 

스프링 DB 2편 - 데이터 접근 활용 기술 - 인프런 | 강의

백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - 강의 소개 | 인

www.inflearn.com

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 가 붙은 클래스는 컴포넌트 스캔의 대상이 된다.
  • @Repository 가 붙은 클래스는 예외 변환 AOP의 적용 대상이 된다.
  • 스프링과 JPA를 함께 사용하는 경우 스프링은 JPA 예외 변환기 ( PersistenceExceptionTranslator )를 등록한다.
  • 예외 변환 AOP 프록시는 JPA 관련 예외가 발생하면 JPA 예외 변환기를 통해 발생한 예외를 스프링 데이터 접근 예외로 변환한다.
  • 결과적으로 리포지토리에 @Repository 애노테이션만 있으면 스프링이 예외 변환을 처리하는 AOP를 만들어준다.

Spring Data Jpa

  • 동적 프록시 기능을 사용해 스프링 데이터 JPA가 인터페이스만 작성해도 구현체가 자동으로 스프링 빈으로 등록된다.
    별도로 개발자가 @Component, @Repository를 작성할 필요가 없다.
  • 가장 대표적인 기능은 공통 인터페이스 기능과 쿼리 메서드 기능이다.
  • 쿼리 메서드 기능 대신에 직접 JPQL을 사용하고 싶을 때는 @Query 와 함께 JPQL을 작성하면 된다.
    이때는 메서드 이름으로 실행하는 규칙은 무시된다.
  • 스프링 데이터 JPAJPQL 뿐만 아니라 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

 

EntityManager thread-safe 문제 - 인프런 | 질문 & 답변

안녕하세요 영한님 !! EntityManager 생성시에, final로 주입받으시거나 @PersistenceContext를 사용해서 주입받으시거나 둘 중에 하나로 코드를 작성하시는 것을 보았는데요!! 제가 알기로는 Entitymanager의 t

www.inflearn.com

https://www.inflearn.com/questions/387369

 

엔티티매니저와 영속성컨텍스트에 관해서 - 인프런 | 질문 & 답변

여러 질문 답변을 찾아보고 내용을 종합해서 이해해본 결과  엔티티매니저와 영속성컨텍스트에 관해서 제가 현재 이해하고 있는게 맞는지 확인 부탁드립니다 ..   엔티티 매니저 1. 언제 생성

www.inflearn.com

 

이상으로 포스팅을 마칩니다. 감사합니다.

 

반응형

댓글