JPA 기본

달래·2024년 2월 14일
0

JPA

목록 보기
2/9

김영한님의 강의를 들으면서, 정리하면서 모르는 것은 보태보았습니다!

EntityManager

스프링을 거치지 않은 순수 JPA 기술이다.

@Entity
@NoArgsConstructor
public class Entity {
	@Id
	private String id;
}
  • @Entity : 이 어노테이션이 있어야 JPA가 있음
  • public/protected 기본 생성자 필수

서비스계층
EntityManager를 주입

  • JPA의 모든 동작이 엔티티 매니저를 통해 이루어짐
  • 내부에 데이터 소스를 가지고 있고, 데이터베이스에 접근

@Transactional

  • 데이터 변경(등록/수정/삭제)는 트랜잭션 안에서 이루어져야 함 (조회 제외)

EntityManager 설정(빈등록)

@Configuration
@RequiredArgsConstructor
public class JpaConfig {
	private final EntityManager em;
	
    @Bean
    public Service service() {
    	return new ServiceImpl(repository());
    }
    
    @Bean
    public Repository repository() {
    	return new JpaRepository(em);
    }
}

객체 저장 persist()

em.persist(Entity entity)

  • JPA에서 객체를 테이블에 저장할 때 사용하는 엔티티매니저 메서드
  • insert문을 실행

객체 조회 find()

Entity entity = em.find(Entity.class, pk) : 조회타입, pk값

  • 엔티티 객체를 pk기준으로 조회
  • Optional.ofNullable(entity) 으로 return해주는게 null방지

객체 목록 조회 createQuery()

String jpql = "select e from Entity e where e.pk = :pk";
TypedQuery<Entity> query = em.createQuery(jpql, Entity.class);
query.setParameter("pk", pk);
return query.getResultList();

JPQL

  • 객체지향쿼리 : String으로 동적 쿼리 생성
  • 여러 데이터를 복잡한 조건으로 조회할 때 사용
  • SQL은 테이블 대상, JPQL은 @Entity객체 대상(SQL과 문법이 거의 비슷)

query.setParameter("pk", pk);

  • 파라미터 바인딩 (:pk 부분을 바인딩한다)

객체 수정 : @Entity 값 수정

Entity entity = em.find(Entity.class, pk);
entity.setPk(requestDto.getPk);
  • entity.setPk(값)
  • JPA는 트랜잭션이 커밋되는 시점에, 변경된 엔티티 객체가 있는지 확인
    변경된 경우 Update SQl을 실행!
    즉, DB에 반영되는 것은 트랜잭션이 커밋될 때이다.

영속성 컨텍스트

예외 처리

@Repository

예외 변환 AOP의 적용 대상이 된다.

스프링과 JPA를 함께 사용하는 경우, 스프링은 PersistenceExceptionTranslator라는 JPA예외변환기를 등록
-> 예외변환AOP프록시는 JPA예외가 발생하면 JPA예외변환기를 통해 발생한 예외를 스프링데이터접근예외로 변환

EntityManager(PersistenceException 발생) -> JpaRepository 예외 전달 -> 예외변환AOP프록시(PersistenceException을 DataAccessException으로 변환) -> 서비스계층에 스프링예외(DataAccessException) 전달. 스프링예외추상화에 의존


2. 스프링 데이터 JPA

JpaRepository 인터페이스

  • 기본적인 CRUD 가능
  • 공통화가능한 기능 모두 포함
public interface Repository extends JpaRepository<Entity, entity타입> {
}

여기서 메소드를 구현하여 사용할 수 있다.

@Repository
@RequiredArgsConstructor
public class RepositoryImpl implements Repository {
	private final EntityManager em;
    ...
}

여기서 @Override하여 사용할 수 있다.

스프링 데이터 JPA의 쿼리메소드 기능

조회

find...By, read...By, query...By, get...By

  • ...에 식별내용이 들어감(생략가능)

find...ByFieldLessThanEqual(Integer field)

  • field보다 작거나 같은것을 조회 (<= )

find...ByFieldLike(String field)

  • field like검색

조합 -> find...ByFieldLikeAndField2LessThanEqual(String field, Integer field2)

count

count...By : long타입

exists

exists...By : boolean타입

삭제

delete...By, remove...By : long타입

distinct

findDistinct, findEntityDistinctBy

limit

findFirst3, findFirst, findTop, findTop3

JPQL 사용

@Query("select e from Entity e where e.pk lik :pk")
List<Entity> findEntities(@Param("pk") Long pk);

파라미터를 명시적으로 바인딩해야함
엔티티 객체를 쿼리하는 방식. 테이블이 대상이 아닌 엔티티를 대상으로 날림

네이티브 쿼리

JPQL 대신 직접 SQL 작성 가능
SQL문을 DB에 직접 날림

단점

메서드 이름으로 쿼리를 실행하면 1)조건이 많을때 메서드이름이 너무 길어지고, 2)조인과같은 복잡한 조건으 사용할 수 없다.
따라서, 복잡해지면 직접 JPQL 쿼리를 작성하는 것이 좋다.

3. Querydsl

동적 쿼리 문제를 해결해줌.

Q타입

컴파일 시점에 자동 생성
QEntity 클래스로 생성된다 -> 버전관리(Git)에 포함되지 않는 것이 좋음!

IntelliJ Idea 컴파일 -> src/main/generated에 파일 생성
gradle 컴파일 -> gradle clean 명령(build.gradle) 설정으로 자동 삭제 가능
clean { delete file('src/main/generated') }

설정

@Configuration
@RequiredArgsConstructor 
public class QuerydslConfig {
	private final EntityManager em;
    
    @Bean
    public EntityService entityService() {
    	return new EntityService1(entityRepository());
    }
    
    @Bean
    public EntityRepository entityRepository() {
    	return new RepositoryImpl(em);
    }
}

RepostioryImpl

JPAQueryFactory가 필요하다. JPQL을 만들기때문에 EntityManager가 필요함.
JPAQueryFactory를 스프링 빈으로 등록해서 사용해도 됨.

@Repository
@Transcational
public class RepositoryIml implements Repository {
	private final EntityManager em;
    private final JPAQueryFactory query;
	
    public RepositoryImpl(EntityManager em){
    	this.em = em;
        this.query = new JPAQueryFactory(em);
    }
}

사용

List<Entity> result = query
		.select(entity)
        .from(entity)
        .where(likeField1(field1))
        .fetch();

...

priavate BooleanExpression likeField1(String field1){
	if (StringUtils.hasText(field1) {
    	return entity.field1.like("%"+field1+"%");
    }
    return null;
}
  • booleanBuilder를 사용해 원하는 where조건을 자바코드로 넣어줄 수 있음.
    where 파라미터에 null을 입력하면 해당 조건은 무시됨.

  • 파라미터에 여러가지 변수를 넣어 줄 수 있다(조건)

  • likeField()등을 다른 쿼리를 작성할 때 재사용 할 수 있음 -> 쿼리 조건 부분모듈화 가능

트레이드 오프

앞과 같이 JpaRepository를 구현하다보면 구조가 복잡해진다.

DI, OCP를 지키기위해 어댑터를 도입하고 더많은 코드를 유지하는 방법을 보통 사용하기 때문이다.

클래스 의존관계
Service -> Repository(i) -> RepositoryImpl -> JpaRepositoryImpl -> 스프링데이터JpaRepository

런타임 객체의존관계
Service -> Repositoryimpl -> 스프링데이터JPA 프록시

하지만, DI/OCP를 포기하고 어댑터를 제거하고 구조를 단순하게 가져가는 방법도 있다.

Service가 직접 스프링데이터Repository를 구현하여 구조를 단순화한다.

클래스 의존관계
Service -> 스프링데이터JpaRepository

구조의 안정성 vs 단순구조/개발편리성

항상 나은 선택을 해야함.
추상화도 비용이 든다!

실용적 구조

Repository를 분리한다.

  • 스프링데이터JPA (단순조회, 기본CRUD)
  • Querydsl (복잡 조회쿼리)
    Service -> 스프링데이터JPA Repository Interface + Querydsl Repository
  1. 스프링데이터JPA Repository
public interface JpaRepository extends JpaRepository<Entity, pk>{
	... 기본CRUD/단순조회쿼리
}
  1. Querydsl Repository
@Repository
public class QueryRepository {
	private final JPAQueryFactory query;
    public QueryRepository(EntityManager em) {
    	this.query = new JPAQueryFactory(em);
    }
    ... 복잡쿼리
}

Config

@Configuration
@RequiredArgsConstructor
public class Config {
	private final EntityManager em;
    private final JpaRepository repository;
    
    @Bean
    public Service service() {
    	return new Service(repository, queryRepository());
    }
    
    @Bean
	public QueryRepository queryRepository() {
    	return new QueryRepository(em);
    }    
    

}

서비스

@Service
@RequiredArgsConstructor
public class ServiceImpl implements Service {
	private final JpaRepository jpaRepository;
    private final QueryRepository queryRepository;
    ...
}

그 외

  • 지연로딩 (lazy loading 개수만큼 쿼리 반복)
  • Fetch join
profile
아좌잣~!

0개의 댓글