스프링 데이터 JPA

Jiwon Park·2023년 6월 6일
0

식별자 직접 생성

<스프링 데이터 JPA 구현체>

public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

	@Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}
}

엔티티에서 @GenerateValue로 식별자 생성할 경우 save() 호출 시점에 식별자가 없으므로 null로 판단해 (원시 타입은 0) 새로운 엔티티로 인식해서 persist로 정상적으로 저장된다.

그러나 @Id 만 사용해서 직접 할당을 하게 될 경우 이미 식별자 값이 있는 상태로 save() 를 호출하므로 merge() 가 호출된다. merge() 는 우선 DB에서 조회 후, 값이 없으면 새로운 엔티티로 인지해서 저장하므로 쿼리가 늘어나 비효율적이다.

따라서 id를 직접 할당할 일이 있을 경우 Persistable 를 사용해서 새로운 엔티티 확인 여부를 직접 구현하는게 효과적이다.

@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {

   @Id
   private String id;
   
   @CreatedDate
   private LocalDateTime createdDate;
   
   public Item(String id) {
      this.id = id;
   }
   
   @Override
   public String getId() {
      return id;
   }
   
   @Override
   public boolean isNew() {
   	  return createdDate == null;
   }
}

등록시간( @CreatedDate )을 조합해서 사용하면 이 필드로 새로운 엔티티 여부를 편리하게 확인할 수 있다. (@CreatedDate에 값이 없으면 새로운 엔티티로 판단)


벌크성 수정 쿼리

@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);

수정 쿼리를 직접 날리면 영속성 컨텍스트를 무시하고 실행하기 때문에, 한 트랜잭션 내의 영속성 컨텍스트에 엔티티가 있는 상태에서 벌크 연산 후 추가 조회를 하면 디비의 수정 값이 아닌 이전의 영속성 컨텍스트 값이 조회된다.

따라서 추가 조회를 하려면 벌크 연산 직후 영속성 컨텍스트를 초기화 한다.


@EntityGraph(LEFT OUTER JOIN) - 연관 엔티티 조회시 N+1해결

//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"})
List<Member> findAll();

//JPQL + 엔티티 그래프
@EntityGraph(attributePaths = {"team"})
@Query("select m from Member m")
List<Member> findMemberEntityGraph();

@EntityGraph(attributePaths = {"team"})
List<Member> findByUsername(String username)

//패치 조인
@Query("select m from Member m left join fetch m.team")
List<Member> findMemberFetchJoin();

쿼리 힌트

readonly -> 디비에서 값 조회 후 영속성 컨텍스트에 값 저장 x

@QueryHints(value = @QueryHint(name = "org.hibernate.readOnly", value = "true"))
Member findReadOnlyByUsername(String username);

@QueryHints(value = { @QueryHint(name = "org.hibernate.readOnly",value = "true")},
			forCounting = true)
Page<Member> findByUsername(String name, Pageable pageable);

Lock

동시성 문제 해결

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByUsername(String name);


Auditing

엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적 (등록일 수정일 등록자 수정자)

-설정
@EnableJpaAuditing - 스프링 부트 설정 클래스에 적용
@EntityListeners(AuditingEntityListener.class) - 엔티티에 적용

-사용 어노테이션
@CreatedDate
@LastModifiedDate
@CreatedBy
@LastModifiedBy

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseTimeEntity {
   @CreatedDate
   @Column(updatable = false)
   private LocalDateTime createdDate;
   @LastModifiedDate
   private LocalDateTime lastModifiedDate;
}

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public class BaseEntity extends BaseTimeEntity {
   @CreatedBy
   @Column(updatable = false)
   private String createdBy;
   @LastModifiedBy
   private String lastModifiedBy;
}

public class Member extends BaseEntity {}

-> 등록시간, 수정시간은 필요하지만 등록자,수정자는 필요없을 수도 있다.
그래서 다음과 같이 Base 타입을 분리하고, 원하는 타입을 선택해서 상속한다.


등록자, 수정자를 처리해주는 AuditorAware 스프링 빈 등록

@EnableJpaAuditing
@SpringBootApplication
public class DataJpaApplication {
   public static void main(String[] args) {
   	  SpringApplication.run(DataJpaApplication.class, args);
   }
   @Bean
   public AuditorAware<String> auditorProvider() {
   	  return () -> Optional.of(UUID.randomUUID().toString());
      //세션 정보나, 스프링 시큐리티 로그인 정보에서 ID 받기
   }
}
profile
안녕하세요

0개의 댓글