스프링 데이터 JPA
리포지토리는 인터페이스만 제공, 스프링이 구현체인 Hibernate는 자동으로 스프링빈으로 등록
스프링 데이터 JPA
가 제공하는 인터페이스를 직접 구현하면, 구현해야 하는 기능이 너무 많다.
사용자가 필요한 기능만 따로 정의한 인터페이스의 구현체를 직접 구현하고 싶을 때 사용
public interface MemberRepositoryCustom {
List<Member> findMemberCustom();
}
@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
private final EntityManager em;
@Override
public List<Member> findMemberCustom() {
return em.createQuery("select m from Member m")
.getResultList();
}
}
참고로 스프링 2.x부터는 사용자 정의 인터페이스 구현체의 이름을
스프링 데이터 JPA를 상속한 인터페이스+Impl 이 아닌 사용자 정의 인터페이스+Impl 로 가능하다.
/**
* 스프링 데이터 JPA 기반 레포지토리
*/
public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
}
스프링 데이터 JPA
가 인식하여 구현체(사용자 정의 구현체)를 스프링 빈으로 자동 등록해주게 된다.
프로젝트, 실무시 참고사항💡
"위의 사용자 정의 인터페이스 구현체 정의 방식"은 Querydsl, springJdbcTemplate등을 함께 사용할때 스프링 데이더 JPA의 인터페이스를 모두 구현하기 어려우니 따로 인터페이스를 생성하여 구현체를 스프링 데이터 JPA가 관리,등록 하도록 사용되어진다.
Querydsl등을 사용하기 위해서 사용자 정의 방식을 사용할때에도 비지니스 로직과 연관된 레포지토리를 사용자 정의 방식으로 사용하는 것이 올바른 설계이다.
만약, 특정 서비스의 화면에 특화된 상황(DTO로 바로 변환)등의 비지니스 로직과는 관련없는 레포지토리 같은 경우에는 사용자 정의 방식보다는 순수 임의의 레포지토리를 만들어 분리하여 설계하는것이 옳다.
서비스를 운영할때 사용자의 기본 로그를 DB에 기록해야할 때가 있다.
예를 들어 작성시간, 변경시간, 작성자, 변경자 등이 있다.
(DB의 트리거와 비슷한 느낌인거 같다.)
그래서 JPA에서는 감시하다라는 의미에 Audit
기능을 제공해준다. 즉 데이터 삽입, 수정시에 자동으로 지정한 컬럼에 값을 넣어주는 기능을 해준다.(시간, 작성자 등)
스프링 데이터 JPA
는 Auditing
기능을 쉬게 사용할 수 있게 제공해준다. 뿐만아니라 순수 JPA
에서도 사용할수 있다.
참고로 순수 JPA에서는 Audigin 기능을 사용하기 위한 주요 어노테이션으로는
@Prepersist
,@PostPersist
,@PreUpdate
,@PostUpdate
등이 있다.
관심있으면 찾아보면 될거 같다.
그럼 스프링 데이터 JPA
에서 제공해주는 Auditing
기능을 살펴보겠다.
@EnableJpaAuditing //Spring Data Jpa - Auditing 사용
@SpringBootApplication
public class SpringDataJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataJpaApplication.class, args);
}
//Spring Data Jpa - Auditing(@CreatedBy, @LastModifiedBy) 사용
@Bean
public AuditorAware<String> auditorProvider() {
return () -> Optional.of(UUID.randomUUID().toString());
}
}
Springboot
설정 클래스에 @EnableJpaAuditing
을 꼭 적어준다.
추 후 뒤에서 나올 @CreatedBy
, @LastModifiedBy
어노테이션을 사용하여
작성자, 수정자를 자동으로 삽입하기 위해서 주체를 지정하기 위해서 AuditorAware<T>
클래스를 선언해주었다.
참고💡
디폴트로 저장시점에 등록일자, 등록자는 물론이고 수정자, 수정일자에도 같은 데이터가 저장한다. 즉 저장시점에 등록일자, 수정일자가 같은 같으로 삽입됨.
@EnableJpaAuditing(modifyOnCreate=false)
라 지정하게 되면 저장시점에 수정자,수정일자들은 null로 설정된다.
위의 코드에서는
AuditorAware<T>
부분을 현재에는 UUID로 임의로 지정하였다.
JWT
기반Spring Security
를 사용할때에는 아래와 같이 SecurityContext에서 Authentication객체를 꺼내와 사용하면 된다.
@EnableJpaAuditing
@SpringBootApplication
public class AmsSideprojectApplication {
public static void main(String[] args) {
SpringApplication.run(AmsSideprojectApplication.class, args);
}
//!!!Test!!!
//Spring Data Jpa - Auditing(@CreatedBy, @LastModifiedBy) 사용
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String name = "";
if(authentication!=null) {
//nickname
// PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal();
// User user = principal.getUser()
// name = user.getNickname();
//email
name = authentication.getName();
}
return Optional.of(name);
}
}
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
}
간단하게 테스트 해보기 위해서 Spring Security
를 구현해논 자체 프로젝트에 적용해보았다.
코드에서 보다싶이 SecurityContext
영역에서 객체를 꺼내 사용할 필드를 지정하였다.
내가 작성한 코드 내에서,
authentication.getName()
는 Email을 반환해주게 된다. 만약 Nickname을 사용하기 위해서는 형변환을 통해 사용자의 닉네임을 가져오면 된다.
Spring Security
의 관한 내용은 아래 블로그에 정리해놓았는데 참고 하시길 바랍니다.
https://velog.io/@rnqhstlr2297/series/Spring-Secuiry%EC%99%80-JWT%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8
@Getter
@MappedSuperclass //엔티티가 아닌 클래스 상속 -> 매핑 정보 속성만 제공
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
}
@Getter
@MappedSuperclass //엔티티가 아닌 클래스 상속 -> 매핑 정보 속성만 제공
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity extends BaseTimeEntity{
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
@EntityListeners
AuditingEntityListener.class
을 지정해주면 됌.@MappedSuperclass
@MappedSuperclass vs @Embeddable+@Embedded 차이점💡 - 추후 정리
@CreatedDate
LastModifiedDate
@CreatedBy
AuditorAware<T>
구현 클래스를 지정해주어야된다. @LastModifiedBy
AuditorAware<T>
구현 클래스를 지정해주어야된다. @Column(updatable = false)
위 코드를 보면 BaseEntity(작성자,수정자), BaseTimeEntity(시간관련)으로 분리한 것을 볼 수 있다.
대부분 엔티티에서 등록시간, 수정시간은 필요하지만 등록자,수정자는 필요하지 않은 경우도 있다.
그래서 클래스를 분리하여 원하는 타입을 선택해서 상속받아 사용하는 방식으로 설게하였다.
//순수 JPA, Spring Dat JPA - Auditing
@Test
public void JpaEventBaseEntity() throws Exception {
Member member = new Member("member1");
memberJpaRepository.save(member); //@PrePersist 실행
Thread.sleep(100);
member.setUsername("member2");
em.flush(); //@PreUpdate 실행
em.clear();
Member findMember = memberJpaRepository.findById(member.getId()).get();
System.out.println("findMember.createdDate = " + findMember.getCreatedDate());
//System.out.println("findMember.updatedDate = " + findMember.getUpdatedDate());
System.out.println("findMember.lastModifiedDate = " + findMember.getLastModifiedDate());
System.out.println("findMember.createdBy = " + findMember.getCreatedBy());
System.out.println("findMember.lastModifiedBy = " + findMember.getLastModifiedBy());
}
아래 처럼 결과가 정상적으로 나오는것을 볼 수 있다.
스프링 데이터가 제공해주는 페이징과 정렬기능을 사용하면 편리하게 스프링 MVC에서 사용할 수 있다.
Pageable
인터페이스를 파라미터와 응답으로 값으로 사용할 수 있게 된다.
코드를 통해 기능을 살펴보자
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
Page<Member> page = memberRepository.findAll(pageable);
return page;
}
파라미터로 Pageable
인터페이스를 사용한 것을 볼 수 있다.
요청 파라미터로 사용하는 예시로는 /members?page=0&size=3&sort=id,desc&sort=username
와 같다.
page 번호와, 개수(size), 정렬조건 등을 쿼리스트링
으로 요청하면 된다.
Pageable 인터페이스
만 파라미터로 적었지만 가능한 이유는,
스프링 부트
가 파라미터로 Regeable 인터페이스
가 있으면 요청한 파라미터 정보를 가지고 구현체인 PageRequest를 생성해서 주입해주기 때문이다.❗
참고💡
추가로Spring Data Jpa
를 사용할때,공통 인터페이스
,Query Method
,NamedQuery
방식 등 사용시 인터페이스 메소드 파라미터로 마지막에Pageable
를 전달하게 되면스프링 데이터
가 자동으로 페이징 처리를 해준다.
스프링 데이터 JPA
가 내부적으로 method(Repositoty에 정의한 쿼리 메소드) 정보를 통해 메소드의 파라미터에 Pageable 타입을 가지는 파라미터를 확인한 후 처리하게 된기 때문이다.
NamedQuery도 동일한 방식으로 동작하게 된다.추후에 구현체를 분석하여 동작방식을 좀 더 상세히 파악할 필요가 있을거 같다🤣
참고💡
Pageable
의 기본 size를 변경하는 방식으로는yml
파일을 직접 설정하는 방식과@PageableDefault
어노테이션을 사용한 방식이 있다. 사용방법은 찾으면 쉽게 나올 것이다.
@GetMapping("/members")
public Page<MemberDto> page (Pageable pageable){
Page<Member> page = memberRepository.findAll(pageable);
return page.map(MemberDto::new);
}
Pageable
인터페이스를 응답으로 사용하게 되면 구현체인 PageRequest
의 필드들이 응답값으로 포함되게 된다.
또한 Page
에map()
기능을 제공하고 있어 쉽게 DTO로 변환이 가능하다.