๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

[Spring] [BaseTimeEntity, JPA Auditing]์„ ํ†ตํ•œ ์ƒ์„ฑ์‹œ๊ฐ„/์ˆ˜์ •์‹œ๊ฐ„ ์ž๋™ํ™”

by dkswnkk 2022. 5. 22.

์„œ๋ก 

๋ณดํ†ต ์—”ํ‹ฐํ‹ฐ(entity)์—๋Š” ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ ์ƒ์„ฑ ์‹œ๊ฐ„๊ณผ ์ˆ˜์ •์‹œ๊ฐ„์„ ํฌํ•จํ•œ ์ปฌ๋Ÿผ์ด ํ•ญ์ƒ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์–ธ์ œ ๋งŒ๋“ค์–ด์กŒ๋Š”์ง€, ์–ธ์ œ ์ˆ˜์ •๋˜์—ˆ๋Š”์ง€ ๋“ฑ์€ ์ฐจํ›„ ์œ ์ง€๋ณด์ˆ˜์— ์žˆ์–ด ๊ต‰์žฅํžˆ ์ค‘์š”ํ•œ ์ •๋ณด์ด๋ฉฐ, ๋ฐ์ดํ„ฐ ๋ถ„์„์— ์œ ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค ๋ณด๋‹ˆ ๋งค๋ฒˆ DB์— ์‚ฝ์ž…(insert) ํ•˜๊ธฐ ์ „, ๊ฐฑ์‹ (update) ํ•˜๊ธฐ ์ „์— ๋‚ ์งœ ๋ฐ์ดํ„ฐ๋ฅผ ๋“ฑ๋ก/์ˆ˜์ •ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์—ฌ๊ธฐ์ €๊ธฐ ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๋งˆ๋‹ค ์ƒ์„ฑ์‹œ๊ฐ„๊ณผ ์ˆ˜์ •์‹œ๊ฐ„์„ ๋„ฃ๋Š”๋‹ค๋ฉด ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ์ง€์ €๋ถ„ํ•ด์ง€๊ณ  ๊ฐ์ฒด์ง€ํ–ฅ์ ์ด์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋Š” JPA Auditing๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

LocalDate ์‚ฌ์šฉ

Java8๋ถ€ํ„ฐ LocalDate์™€ LocalDateTime์ด ๋“ฑ์žฅํ–ˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์‹ค ๊ทธ๋™์•ˆ์€ Java์˜ ๊ธฐ๋ณธ ๋‚ ์งœ ํƒ€์ž…์ธ Date๋ฅผ ์‚ฌ์šฉํ•ด ์™”์ง€๋งŒ Java8 ์ดํ›„๋ถ€ํ„ฐ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ๋˜ Date๋ฅผ ๊ณ ์นœ LocalDate์™€ LocalDateTime๋ฅผ ๋ฌด์กฐ๊ฑด ์‚ฌ์šฉํ•˜์…”์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Java8์ด ๋‚˜์˜ค๊ธฐ ์ „๊นŒ์ง€ ์‚ฌ์šฉ๋˜์—ˆ๋˜ Date์™€ Calendar ํด๋ž˜์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ์ ๋“ค์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋ถˆ๋ณ€(๋ณ€๊ฒฝ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ) ๊ฐ์ฒด๊ฐ€ ์•„๋‹ˆ๋‹ค.
 -> ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ์–ธ์ œ๋“  ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
Calendar๋Š” ์›”(Month) ๊ฐ’ ์„ค๊ณ„๊ฐ€ ์ž˜๋ชป๋˜์—ˆ๋‹ค.
 -> 10์›”์„ ๋‚˜ํƒ€๋‚ด๋Š” Calendar.OCTOBER์˜ ์ˆซ์ž ๊ฐ’์€ '10'์ด ์•„๋‹ˆ๋ผ '9'๋กœ ์„ค๊ณ„๋˜์–ด์žˆ๋‹ค.

๋”ฐ๋ผ์„œ ๊ทธ๋™์•ˆ JodaTime์ด๋ผ๋Š” ์˜คํ”ˆ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฌธ์ œ์ ์„ ํ”ผํ–ˆ์—ˆ์ง€๋งŒ, Java8 ์ดํ›„๋ถ€ํ„ฐ๋Š” LocalDate๋ฅผ ํ†ตํ•˜์—ฌ ํ•ด๊ฒฐ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋„ค์ด๋ฒ„ D2์—์„œ ์ฐธ๊ณ ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. https://d2.naver.com/helloworld/645609

๋˜ํ•œ LocalDate์™€ LocalDateTime์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ œ๋Œ€๋กœ ๋งคํ•‘๋˜์ง€ ์•Š๋Š” ์ด์Šˆ๋Š” Hibernate 5.2.10 ๋ฒ„์ „์—์„œ ํ•ด๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์Šคํ”„๋ง ๋ถ€ํŠธ 1.x๋ฅผ ์“ด๋‹ค๋ฉด ๋ณ„๋„๋กœ Hibernate 5.2.10 ๋ฒ„์ „ ์ด์ƒ์„ ์‚ฌ์šฉํ•˜๋„๋ก ์„ค์ •์ด ํ•„์š”ํ•˜์ง€๋งŒ, ์Šคํ”„๋ง ๋ถ€ํŠธ 2.x ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•ด๋‹น ๋ฒ„์ „์„ ์‚ฌ์šฉ ์ค‘์ด๋ผ ๋ณ„๋‹ค๋ฅธ ์„ค์ • ์—†์ด ๋ฐ”๋กœ ์ ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์˜ˆ์ œ

@Getter
@MappedSuperclass // BaseEntity๋ฅผ ์ƒ์†ํ•œ ์—”ํ‹ฐํ‹ฐ๋“ค์€ ์•„๋ž˜ ํ•„๋“œ๋“ค์„ ์ปฌ๋Ÿผ์œผ๋กœ ์ธ์‹ํ•˜๊ฒŒ ๋œ๋‹ค.
@EntityListeners(AuditingEntityListener.class)  // Auditing(์ž๋™์œผ๋กœ ๊ฐ’ ๋งคํ•‘) ๊ธฐ๋Šฅ ์ถ”๊ฐ€
public abstract class BaseTimeEntity {

    @CreatedDate
    private LocalDateTime createdTime;

    @LastModifiedDate
    private LocalDateTime lastModifiedTime;
}

๋จผ์ € BaseTimeEntity๋ฅผ ์ถ”์ƒ ํด๋ž˜์Šค ํ˜•ํƒœ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. BaseTimeEntity ํด๋ž˜์Šค๋Š” ๋ชจ๋“  Entity์˜ ์ƒ์œ„ ํด๋ž˜์Šค๊ฐ€ ๋˜์–ด Entity๋“ค์˜ ์ƒ์„ฑ ์‹œ๊ฐ„๊ณผ, ์ˆ˜์ • ์‹œ๊ฐ„์„ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์—ญํ• ์ž…๋‹ˆ๋‹ค.

๊ฐ ์ฝ”๋“œ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ค๋ช…ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • @MappedSuperclass: JPA Entity ํด๋ž˜์Šค๋“ค์ด BaseTimeEntity์„ ์ƒ์†ํ•  ๊ฒฝ์šฐ ํ•„๋“œ๋“ค(createdTime, lastModifiedTime)๋„ ์ปฌ๋Ÿผ์œผ๋กœ ์ธ์‹ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • @EntityListeners(AuditingEntityListener.class): BaseTimeEntity ํด๋ž˜์Šค์— Auditing ๊ธฐ๋Šฅ์„ ํฌํ•จ์‹œํ‚ต๋‹ˆ๋‹ค.
  • @CreatedDate: Entitiy๊ฐ€ ์ตœ์ดˆ๋กœ ์ƒ์„ฑ๋˜์–ด ์ €์žฅ๋  ๋•Œ ์‹œ๊ฐ„์ด ์ž๋™์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.
  • @LastModifiedDate: ์กฐํšŒํ•œ Entity์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ ์‹œ๊ฐ„์ด ์ž๋™์œผ๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

 

@Entity
@AllArgsConstructor
@NoArgsConstructor
public class User extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;
    private String password;

    @Builder
    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

 

๊ทธ ํ›„ Entity ํด๋ž˜์Šค์ธ User๊ฐ€ BaseTimeEntity๋ฅผ ์ƒ์†๋ฐ›๋„๋ก ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

@SpringBootApplication
@EnableJpaAuditing
public class ApiPracticeApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiPracticeApplication.class, args);
    }

    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.KOREA);
        return localeResolver;
    }

}

๋งˆ์ง€๋ง‰์œผ๋กœ JPA Auditing ์–ด๋…ธํ…Œ์ด์…˜๋“ค์„ ๋ชจ๋‘ ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ๋„๋ก Apllication ํด๋ž˜์Šค์— ์œ„์™€ ๊ฐ™์ด ํ™œ์„ฑํ™” ์–ด๋…ธํ…Œ์ด์…˜์ธ @EnableJpaAuditing์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

 

ํ…Œ์ŠคํŠธ

@SpringBootTest
class UserTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void BaseEntity_ํ…Œ์ŠคํŠธ(){
        // given
        LocalDateTime nowTime = LocalDateTime.now();

        User user = User.builder()
                .name("์•ˆ์ฃผํ˜•")
                .password("password1")
                .build();

        //when
        userRepository.save(user);

        //then
        assertThat(user.getCreatedTime()).isAfter(nowTime);

    }

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. nowTime์€ userํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ์ด์ „์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— userํด๋ž˜์Šค์˜ createdTime ๋ณด๋‹ค ํ•ญ์ƒ ์ž‘๊ฒŒ ๋‚˜์™€์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ assertThat(user.getCreatedTime()).isAfter(nowTime); ์„ ํ†ตํ•ด ๊ฒ€์ฆ์„ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ

ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰๊ฒฐ๊ณผ๋Š” ์˜ˆ์ƒํ–ˆ๋˜ ๋Œ€๋กœ ๋ฌด์‚ฌํžˆ ํ†ต๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

 

์ฐธ๊ณ 

์Šคํ”„๋ง ๋ถ€ํŠธ์™€ AWS๋กœ ํ˜ผ์ž ๊ตฌํ˜„ํ•˜๋Š” ์›น ์„œ๋น„์Šค(3.5์žฅ) - ์ด๋™์šฑ

๋Œ“๊ธ€