Spring boot hibernate jpa에서 Auditing 사용 - update, create 시간 자동 변경
web/Spring

Spring boot hibernate jpa에서 Auditing 사용 - update, create 시간 자동 변경

반응형

엔티티를 만들고 데이터를 삽입하고 조작할 때 create date와 last modified date를 별도로 업데이트 해주면서 관리하였다.

하지만 이번에 JPA를 공부하면서 별도의 작업 없이 JPA의 Auditing 기능을 사용하면 데이터를 삽입하고 수정할 때 자동으로 날짜를 수정하도록 할 수 있는 기능이 있는 것을 확인했다.


1. Configuration

JPA Auditing을 사용하기 위해서는 기능을 자동으로 활성화 해주는 어노테이션을 붙혀주면 된다. 처음에는 @Configuration을 사용하는 클래스에 함께 선언해주었는데 정상적으로 적용이 되지 않아서 @SpringBootApplication을 사용하는 곳에 적용했더니 성공적으로 적용되었다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.wedul.springboottest;
 
import com.wedul.common.exception.ValidationException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.web.context.request.WebRequest;
 
import java.util.Map;
 
@SpringBootApplication
@EnableJpaAuditing
public class SpringboottestApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SpringboottestApplication.class, args);
    }
 
    @Bean
    public ErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes() {
 
            @Override
            public Map<String, Object> getErrorAttributes(WebRequest webRequest,
                                                          boolean includeStackTrace) {
                Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace);
                Throwable error = getError(webRequest);
 
                // validatijon Exception에 경우 별도의 처리를 진행한 에러 데이터 추가
                if (error instanceof ValidationException) {
                    errorAttributes.put("errors", ((ValidationException)error).getErrors());
                }
                return errorAttributes;
            }
 
        };
    }
}
 
cs


2. Entity에 CreatedDate, LastModifiedDate

Auditing을 사용할 엔티티에는 몇가지 어노테이션이 사용된다.

먼저 설정을위해서 사용되는 @MappedSuperclass, @EntityListeners(AuditingEntityListener.class)이다. 첫 번재 어노테이션은 이후에 사용될 createdDate, modifiedDate와 같은 필드들을 컬럼으로 인식하게 도와주는 역할을 하고 두 번째 어노테이션은 해당 Entity에 Auditing기능을 포함한다라는 명시를 한다.

처음에는 필요한 Entity에만 CreatedDate, lastModifiedDate가 포함하도록 Entity마다 기재해주었다.하지만 많은 Entity에서 필요로 할 것 같고 필요할 때마다 새로 써주기가 비효율적인 것 같아서 추상클래스로 만들고 필요한 Entity에서 이를 상속받아서 사용하도록 하였다.



- 추상클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.wedul.common.dto;
 
import lombok.Getter;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
 
/**
 * 모든 Entity들의 상위 클래스가 되어 Entity들의 createdDate, modifiedDate를 자동으로 관리
 *
 * @author wedul
 * @since 2018. 8. 14.
 **/
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class TimeEntity {
 
    @CreatedDate
    private LocalDateTime createdDate;
 
    @LastModifiedDate
    private LocalDateTime modifiedDate;
 
}
cs


- 상속받아서 사용하는 Product Entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.wedul.springboottest.product.dto;
 
import com.wedul.common.dto.BaseTimeEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
import javax.persistence.*;
import java.io.Serializable;
import java.sql.Timestamp;
 
/**
 * 상품 정보
 *
 * @author wedul
 * @since 2018. 08. 12
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity // class (hibernate)
@Table(name = "product")
public class ProductDto extends TimeEntity implements Serializable {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long productId;
 
    @Column(nullable = false, unique = true)
    private String productName;
 
    @Column(nullable = false)
    private long price;
 
    public ProductDto(ProductRequestDto req) {
        this.productName = req.getProductName();
        this.price = req.getPrice();
    }
 
}
 
cs


테스트 코드를 작성하여 정상적으로 시간값이 들어가고 또 변경되는지 확인해보자.

1. 처음 데이터 삽입시 시간값 입력 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * Product Test
 *
 * @author wedul
 * @since 2018. 8. 14.
 **/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest // rolleback 설정
@Rollback(value=true)
public class ProductTest {
 
    private MockMvc mockMvc;
    private final MediaType mediaType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
 
    @Autowired
    ProductCtrl productCtrl;
 
    @Autowired
    ProductServiceI productService;
 
    @Before // before 클래스는 한번만 실해되고 before는 각 테스트마다 실행된다.
    public void beforeClass() {
        this.mockMvc = standaloneSetup(productCtrl).build();
    }
 
    @Test
    public void insertTest_reqestBody() throws Exception {
 
        // Mock Request Builder
        MockHttpServletRequestBuilder req =
                post("/api/product/new")
                        .content(CommonUtil.getJsonStrFromObject(new ProductRequestDto("i-mac", 2220011L)))
                        .contentType(mediaType);
 
        // 테스트
        MvcResult result = mockMvc.perform(req).andExpect(status().isOk()).andReturn();
        ResultDto resultDto = CommonUtil.getObjectFromJsonStr(result.getResponse().getContentAsString(), ResultDto.class);
 
        assertTrue(resultDto.isResult());
    }
}
cs


정상적으로 날짜값이 들어가 있는 것을 확인 할 수있다. 이제 수정 테스트를 진행해보자.


2. 수정 후 last modified date 시간 변경여부 테스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void updateTest_requestBody() throws Exception {
    // Mock Request Builder
    MockHttpServletRequestBuilder req =
           put("/api/product/edit")
           .content(CommonUtil.getJsonStrFromObject(new ProductRequestDto(1,"macbook pro", 2312111L)))
           .contentType(mediaType);
 
    // 테스트
    MvcResult result = mockMvc.perform(req).andExpect(status().isOk()).andReturn();
    ResultDto resultDto = CommonUtil.getObjectFromJsonStr(result.getResponse().getContentAsString(), ResultDto.class);
 
    assertTrue(resultDto.isResult());
}
cs


업데이트 후에 정상적으로 modified_date만 변경된 것을 확인할 수있다.

좋은 기능인것같다. JPA를 공부하고 있는데 Mybatis보다 솔직히 불편하다 그런데 한번 잘 구축해놓으면 편하기는 하다. 그래도 무엇하나 변경된다면 결국 너무 손이 많이가고 러닝커브가 좀 심하다.

흠 더 사용해보고 실무에서 써봐야 이걸 왜 요새 사용하는지 더 이해할 수 있을 것 같다.

자세한 코드는 github 참조
https://github.com/weduls/spring_boot_test


반응형