본문 바로가기

Springboot

QueryDSL, Auditing, Dynamic Insert/Update

QueryDSL : Entity 의 매핑정보를 활용하여 쿼리에 적합하도록 쿼리 전용 클래스(Q클래스)로 재구성해주는 기술

 - JPAQueryFactory 을 통한 Q클래스를 활용할 수 있는 기능들을 제공

 

JPAQueryFactory : 재구성한 Q클래스를 통해 문자열이 아닌 객체 또는 함수로 쿼리를 작성하고 실행하게 해주는 기술

 

@PersistenceContext
EntityManager em;
 
public List<User> selectUserByUsernameAndPassword(String username, String password){
	JPAQueryFactory jqf = new JPAQueryFactory(em);
	QUser user = QUser.user;
  
	List<Person> userList = jpf
			.selectFrom(user)
			.where(person.username.eq(username)
			.and(person.password.eq(password))
			.fetch();
                                
	return userList;
}

 

QueryDSL 적용 방법

JPAQueryFactory 사용을 위해 추가해야할 코드

JPAQueryFactory 에 entityManager 를 주입해서 Bean 으로 등록해줘야 한다

// configuration 패키지안에 추가

@Configuration
public class JPAConfiguration {

  @PersistenceContext
  private EntityManager entityManager;

  @Bean
  public JPAQueryFactory jpaQueryFactory() {
    return new JPAQueryFactory(entityManager);
  }
}

 

 

테이블 객체 방명록 설정하기 (Auditing)

Auditing : 사용하면 엔티티를 누가 언제 생성/마지막 수정 했는지 자동으로 기록되게 할 수 있다.

@CreatedDate
private Date created;

@LastModifiedDate
private Date updated;

@CreatedBy
@ManyToOne
private Account createdBy;

@LastModifiedBy
@ManyToOne
private Account updatedBy;

 

Auditing 적용 방법

 

1. 메인 애플리케이션 위에 @EnableJpaAuditing 추가

@EnableJpaAuditing
@SpringBootApplication
public class Application {

 

2. 엔티티 클래스 위에 @EntityListeners(AuditingEntityListener.class) 추가

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class TimeStamp {
    @CreatedDate
    private LocalDateTime createdAt;

    @CreatedBy
    @ManyToOne
    private User createdBy;

    @LastModifiedDate
    private LocalDateTime modifiedAt;

    @LastModifiedBy
    @ManyToOne
    private User modifiedBy;
}

 

3. AuditorAware 구현체 만들기

 1) createdAt, modifiedAt 은 구현체 없이 동작하지만 createdBy, modifiedBy 는 구현체가 필요하다.

 2) SpringSecurity 의 SecurityContextHolder 에서 인증정보안에 담긴 UserDetailsImpl 을 사용하여 user 객체를 가져와서         넣어준다.

SpringSecurity의 JwtFilter에서 우리가 저장해주는 부분 코드

// JwtAuthFilter.java

@Slf4j
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = jwtUtil.resolveToken(request);

        if(token != null) {
            if(!jwtUtil.validateToken(token)){
                jwtExceptionHandler(response, "Token Error", HttpStatus.UNAUTHORIZED.value());
                return;
            }
            Claims info = jwtUtil.getUserInfoFromToken(token);
						// 인증정보 세팅함수 호출
            setAuthentication(info.getSubject());
        }
        try {
            filterChain.doFilter(request, response);
        }catch(FileUploadException e){
            jwtExceptionHandler(response,"File Upload Error",400);
        }
    }

    public void setAuthentication(String username) {
				// SecurityContextHolder 는 threadLocal 로 구현되어 요청쓰레드내에서 공유할 수 있다.
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = jwtUtil.createAuthentication(username);
				// 요기서 인증정보(계정정보)를 담아준다.
        context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);
    }
		...
}

 

@Service
public class UserAuditorAware implements AuditorAware<User> {
    @Override
    public Optional<User> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }

        return Optional.of(((UserDetailsImpl) authentication.getPrincipal()).getUser());
    }
}

 

4. @EnableJpaAuditing에 Auditor 빈 이름 설정하기

@EnableJpaAuditing(auditorAwareRef = "userAuditorAware") // auditorAware 의 빈이름을 넣어준다.
@SpringBootApplication
public class Application {

 

 

필요한 부분만 갱신하기 (Dynamic Insert/ Update)

 

@DynamicInsert

- 이 어노테이션을 엔티티에 적용하게 되면 Insert 쿼리를 날릴 때 null 인 값은 제외하고 쿼리문이 만들어진다

 

적용 방법

- Entity에 @DynamicInsert 어노테이션 붙여주면 끝

@DynamicInsert
	public class User {
  ...
}

 

테스트 실습 코드 

@Test
  void dynamicInsertTest() {
    // given
    var newUser = User.builder().username("user").build();

    // when
    userRepository.save(newUser);

    // then
    // 부분 생성 쿼리
  }

 

적용 전

Hibernate: 
    insert 
    into
        users
        (password, username, id) 
    values
        (?, ?, ?)  // 141ms 소요

 

적용 후

Hibernate: 
    insert 
    into
        users
        (username, id) 
    values
        (?, ?)  // 133ms 소요

 

 

@DynamicUpdate

- 이 어노테이션을 엔티티에 적용하게 되면 Update 쿼리를 날릴 때 null인 값은 제외하고 쿼리문이 만들어진다.

 

적용 방법

@DynamicUpdate
public class User {
  ...
}

 

테스트 실습 코드

@Test
  void dynamicUpdateTest() {
    // given
    var newUser = User.builder().username("user").password("password").build();
    userRepository.save(newUser);

    // when
    newUser.updatePassword("new password");
    userRepository.save(newUser);

    // then
    // 부분 수정 쿼리
  }

 

적용 전

Hibernate: 
    update
        users 
    set
        password=?,
        username=? 
    where
        id=?  // 149ms

 

적용 후

Hibernate: 
    update
        users 
    set
        password=? 
    where
        id=?  // 134ms