몬그로이

Entity-Table 에 사용하기 좋은 어노테이션 기능 본문

Organizing Docs/Spring Docs

Entity-Table 에 사용하기 좋은 어노테이션 기능

Mon Groy 2024. 6. 30. 20:00

<contents>

 

1. Auditing

- 기본 사용법

- 감사기능(By)까지 추가하여 사용하는 방법 두 가지

 

2. Dynamic Insert/Update


Auditing

누가/언제 생성/수정 했는지 Column에 기록하는 기능

 

1. 메인 application 에 @EnableJpaAuditing 를 달아준다

@EnableJpaAuditing

@SpringBootApplication

public class Application {

 

2. Auditing 기능을 사용할 Entity 클래스 위에 @EntityListeners(AudigingEntity.class) 를 달아준다

@Getter

@MappedSuperclass

@EntityListeners(AuditingEntityListener.class)

public class TimeStamp {

 

 

3. Auditing 기능을 적용할 변수 위에 사용할 기능에 맞춰 @CreatedDate 등을 위에 적어준다

@Getter

@MappedSuperclass

@EntityListeners(AuditingEntityListener.class)

public class TimeStamp {

 

     @CreatedDate

      private LocalDateTime createdAt;

 

      @LastModifiedDate

      private LocalDateTime modifiedAt;

 }

 

* createdAt, modifiedAt 은 구현체 없이 동작하지만 createdBy, modifiedBy 는 구현체가 필요함

즉, By는 '누군가에 의해서' 그렇게 됐는지를 명시하는 것이므로, '누구'인지 확인하는 절차가 들어간 로직이 필요함

 

Spring Data JPA에서 사용자 정의 감사 기능을 추가하기

1. AuditorAware 인터페이스의 구현체를 만들어 적용하는 방법 - 예시 2가지

2. @EntityListeners 를 사용하여 Entity 리스너를 등록하는 방법

   - Entity 리스너만으로 감사 기능 구현하기 

   - AuditorAware 와 Entity 리스너 를 이용하여 감사 기능 구현하기


 AuditorAware 인터페이스의 구현체를 만드는 방법 - 예시1

 

1. AuditorAware 인터페이스

public interface AuditorAware<T> {

      Optional<T> getCurrentAuditor(); // 단일 메서드 getCurrentAuditor()만을 정의하는 인터페이스

}

 

2. AuditorAware 구현체 생성

실제로 사용할 사용자 정보를 가져오는 로직 작성

public class SpringSecurityAuditorAware implements AuditorAware<String> {

 

@Override

public Optional<String> getCurrentAuditor() {

       // Spring Security를 통해 인증된 사용자의 이름을 가져옴

       return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())

                                  .filter(authentication -> authentication.isAuthenticated())

                                   .map(authentication -> {

                                    Object principal = authentication.getPrincipal();

                                         if (principal instanceof UserDetails) {

                                           return ((UserDetails) principal).getUsername();

                                           } else {

                                           return principal.toString();

                                           }

                                    });

       }

}

* HttpServletRequest를 통한 사용자 정보 가져오거나 다른 다양한 방법들도 가능함

 

3. @EnableJpaAuditing 에 AuditorAware 빈 이름 등록 - Spring 이 관리해야 사용되므로

AuditorAware  를 @Component나 @Configuration을 통해 빈으로 등록하는 절차

@Configuration

@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")

public class JpaAuditingConfiguration {

      

       @Bean

       public AuditorAware<String> springSecurityAuditorAware() {

       return new SpringSecurityAuditorAware();

       }

}


 AuditorAware 인터페이스의 구현체를 만드는 방법 - 예시2

pringSecurity 의 SecurityContextHolder 에서 인증정보안에 담긴 UserDetailsImpl 을 사용하여

user 객체를 가져와서 넣어준다

// 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);

}

...

}

 

2.

@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());

}

}

 

@EnableJpaAuditing(auditorAwareRef = "userAuditorAware") // auditorAware 빈이름을 넣어준다.

@SpringBootApplication

public class Application {


위 두 예시 이후 

@Entity

@Table(name = "members")

@EntityListeners(AuditingEntityListener.class) // 커스텀 하기 전과 같이 이렇게만 적어줘도 By 기능까지 사용 가능

public class Member {

 

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

 

private String username;

 

// 감사 정보

@CreatedDate

private LocalDateTime createdAt;

 

@LastModifiedDate

private LocalDateTime modifiedAt;

 

@CreatedBy

private String createdBy;

 

@LastModifiedBy

private String modifiedBy;

}


@EntityListeners 만 사용하여 Entity 리스너를 등록하는 방법

 

1. AuditEntityListener 클래스를 생성하여 정의하기

@PrePersist와 @PreUpdate 어노테이션을 사용하여 엔티티의 생성 및 수정 시점에 필요한 작업을 수행

@Component

public class AuditEntityListener {

 

      @PrePersist

       public void prePersist(Object target) {

              if (target instanceof Auditable) {

                    Auditable auditable = (Auditable) target;

                     LocalDateTime now = LocalDateTime.now();

                     auditable.setCreatedAt(now);

                     auditable.setModifiedAt(now);

              }

       }

      

       @PreUpdate

       public void preUpdate(Object target) {

              if (target instanceof Auditable) {

                     Auditable auditable = (Auditable) target;

                     auditable.setModifiedAt(LocalDateTime.now());

              }

       }

}

 

2. Auditable 인터페이스 생성

감사 기능을 적용할 Auditable 인터페이스를 정의

이 인터페이스는 생성일자(createdAt)와 수정일자(modifiedAt)를 관리할 수 있는 메서드를 제공함

import java.time.LocalDateTime;

 

public interface Auditable {

 

      LocalDateTime getCreatedAt();

 

       void setCreatedAt(LocalDateTime createdAt);

 

       LocalDateTime getModifiedAt();

 

       void setModifiedAt(LocalDateTime modifiedAt);

}

 

3. 엔티티에 감사 기능 적용

@Entity

@EntityListeners(AuditEntityListener.class) // 엔티티 리스너 등록

public class Member implements Auditable {

 

       @Id

       @GeneratedValue(strategy = GenerationType.IDENTITY)

       private Long id;

 

      private String username;

 

       private LocalDateTime createdAt;

 

       private LocalDateTime modifiedAt;

 

       // Getter, Setter 생략

 

       @Override

       public LocalDateTime getCreatedAt() {

       return createdAt;

       }

 

       @Override

       public void setCreatedAt(LocalDateTime createdAt) {

       this.createdAt = createdAt;

       }

 

       @Override

       public LocalDateTime getModifiedAt() {

       return modifiedAt;

       }

 

       @Override

       public void setModifiedAt(LocalDateTime modifiedAt) {

       this.modifiedAt = modifiedAt;

       }

}

 

 


 AuditorAware  EntityListener 를 함께 이용하여 감사 기능 구현하기

 

1. AuditorAware 인터페이스 구현

public class CustomAuditorAware implements AuditorAware<String> {

 

@Override

public Optional<String> getCurrentAuditor() {

// 사용자 식별자 또는 이름을 반환하는 로직을 구현

return Optional.of("System"); // 예시로 "System" 고정적으로 반환

}

}

 

2. Entity 리스너 구현

@Component

public class AuditingEntityListener {

 

@Autowired

private CustomAuditorAware customAuditorAware;

 

@PrePersist

public void setCreatedBy(Object entity) {

if (entity instanceof Auditable) {

Auditable auditable = (Auditable) entity;

auditable.setCreatedBy(customAuditorAware.getCurrentAuditor().orElse("System"));

}

}

 

@PreUpdate

public void setModifiedBy(Object entity) {

if (entity instanceof Auditable) {

Auditable auditable = (Auditable) entity;

auditable.setModifiedBy(customAuditorAware.getCurrentAuditor().orElse("System"));

}

}

}

 

3. Entity에 EntityListeners 등록

@Entity

@Table(name = "members")

@EntityListeners(AuditingEntityListener.class)

public class Member implements Auditable {

 

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

 

private String username;

 

private LocalDateTime createdAt;

 

private LocalDateTime modifiedAt;

 

private String createdBy;

 

private String modifiedBy;

 

// Getters and Setters 생략

}

 


 Dynamic Insert/Update

@DynamicInsert 와 @DynamicUpdate 를 Entity 위에 달아주면

Insert 쿼리 사용시 null 인 값을 제외하고 쿼리문이 만들어짐

필드가 많을 수록 효과적

 

@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 예시

@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

 

 

'Organizing Docs > Spring Docs' 카테고리의 다른 글

Jpa repository 메서드 명명규칙 - 공식문서 참고  (0) 2024.07.12
QueryDSL  (0) 2024.06.30
MVC 의 annotation 들  (0) 2024.05.25
Bean의 생명주기와 그것을 알게 되었을 때의 이점  (0) 2024.05.23
HTTP 메서드  (0) 2024.05.20