๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
  • ์žฅ์›์ต ๊ธฐ์ˆ ๋ธ”๋กœ๊ทธ
๐Ÿ“š ์‹œ๋ฆฌ์ฆˆ/- ๋ฐฐ์›Œ๋ณด์ž Spring Data JPA

[๋ฐฐ์›Œ๋ณด์ž Spring Data JPA] JPA Auditing ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด์„œ ์ƒ์„ฑ, ์ˆ˜์ • ์ผ์ž ์ž๋™ํ™”ํ•˜๊ธฐ

by Wonit 2021. 4. 11.

ํ•ด๋‹น ๊ธ€์€ ๋ฐฐ์›Œ๋ณด์ž Spring Data JPA ์‹œ๋ฆฌ์ฆˆ ์ž…๋‹ˆ๋‹ค.
ํ•ด๋‹น ์‹œ๋ฆฌ์ฆˆ์˜ ๋‚ด์šฉ์ด ์ด์–ด์ง€๋Š” ํ˜•ํƒœ์ด๋ฏ€๋กœ ๊ธ€์˜ ๋‚ด์šฉ ์ค‘์— ์ƒ๋žต๋˜๋Š” ๋ง๋“ค์ด ์žˆ์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ์ž์„ธํ•œ ์‚ฌํ•ญ์€ ์•„๋ž˜ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!


์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•  ๋•Œ ์‚ฌ์šฉ์ž์˜ ๊ธฐ๋ณธ์ ์ธ ๋กœ๊ทธ๋ฅผ DB์— ๋‚จ๊ฒจ์•ผ ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค.

 

์ด๋ฅผํ…Œ๋ฉด ๋งˆ์ง€๋ง‰ ๋กœ๊ทธ์ธ ์‹œ๊ฐ„์ด๋ผ๋˜์ง€ ์—”ํ‹ฐํ‹ฐ ์ƒ์„ฑ ์‹œ๊ฐ„, ๋ณ€๊ฒฝ๋œ ์‹œ๊ฐ„๊ณผ ๋ณ€๊ฒฝํ•œ ์‚ฌ๋žŒ์˜ ์ด๋ฆ„๋“ฑ๋“ฑ.

 

์˜ˆ๋ฅผ ๋“ค์–ด๋ณด์ž.

public class User {
  private Long id;
  private String name;
  private String address;
}

public class OrderItem {
  private Long id;
  private User userId;
  private Item itemId;
}

public class Item {
  private Long id;
  private String name;
  private String description;
}

์ด์™€ ๊ฐ™์€ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์กด์žฌํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

 

์˜ˆ๋ฅผ ๋“ค์–ด ๊ฐ๊ฐ์˜ ์ƒ์„ฑ ์‹œ๊ฐ„์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

public class User {
  private Long id;
  private String name;
  private String address;
  private LocalDateTime createdAt;
  private String createdBy;
}

public class OrderItem {
  private Long id;
  private User userId;
  private Item itemId;
  private LocalDateTime createdAt;
  private String createdBy;
}

public class Item {
  private Long id;
  private String name;
  private String description;
  private LocalDateTime createdAt;
  private String createdBy;
}

์ด๋Ÿด ๊ฒฝ์šฐ ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๊ฐ€ LocalDateTime createdAt, String createdBy ์— ๋Œ€ํ•œ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.

 

๊ทผ๋ฐ ์ƒ๊ฐ ํ•œ ๋ฒˆ ํ•ด๋ณด์ž. ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋™์ผํ•œ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค๋ฉด ์—ฐ์‚ฐ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋ณธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งŒ๋“ค๊ณ  ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์š”์ฒญ์‹œ์— ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—…๋ฐ์ดํŠธ๋œ๋‹ค๋ฉด ๋˜์ง€ ์•Š์„๊นŒ?

์ด ๋•Œ ์ฆ‰, ์ƒ์„ฑ์ผ/์ˆ˜์ •์ผ/์ƒ์„ฑ์ž๋ฅผ ์ž๋™ํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๋ฐ”๋กœ JPA Auditing ์ด๋‹ค.

 

Spring Data JPA ์—์„œ์˜ Auditing

Spring Data JPA ์—์„œ๋Š” ์œ„์˜ Auditing ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

 

Auditing์€ Spring Data JPA ์—์„œ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋…์€ ์•„๋‹ˆ๋‹ค.

 

JPA ์ž์ฒด์ ์œผ๋กœ๋„ Auditing ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์ง€๋งŒ Spring Data JPA ์—์„œ๋Š” ๋” ๊น”๋”ํ•˜๊ณ  ์‰ฝ๊ฒŒ ์ œ๊ณตํ•œ๋‹ค.

 

ํ˜„์žฌ ์‹œ๋ฆฌ์ฆˆ์˜ ๋ชฉ์ ์€ Spring Data JPA๋ฅผ ํ•™์Šตํ•˜๋Š” ๊ฒƒ์ด๋ฏ€๋กœ JPA ์—์„œ์˜ Auditing ์— ๋Œ€ํ•ด์„œ๋Š” ์ƒ๋žตํ•˜๊ฒ ๋‹ค.
๋งŒ์•ฝ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด @PrePersist, @PostPersist, @PreUpdate, @PostUpdate ํ‚ค์›Œ๋“œ๋กœ ์ฐพ์•„๋ณผ ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.

@EnableJpaAuditing ์‚ฌ์šฉํ•˜๊ธฐ

Spring Data JPA ์—์„œ JPA ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” SpringBoot ์„ค์ • ํด๋ž˜์Šค์— @EnableJpaAuditing ์„ ์ ์–ด์ค˜์•ผํ•œ๋‹ค.

 

๋ณดํ†ต Springboot ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ํด๋ž˜์Šค ์ƒ๋‹จ์— ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณ ๋Š” ํ•œ๋‹ค.

@EnableJpaAuditing
@SpringBootApplication
public class DatajpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(DatajpaApplication.class, args);
    }

}

๊ทธ๋ฆฌ๊ณ  ์šฐ๋ฆฌ๊ฐ€ Auditing์„ ํ•  ํ•„๋“œ๋ฅผ ๊ฐ–๋Š” ๊ธฐ๋ณธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ƒ์„ฑํ•˜์ž.

 

์ฒ˜์Œ ๋ณด๋Š” ์ฝ”๋“œ๋“ค์ด ๋‚˜์˜ค๋”๋ผ๋„ ๊ฑฑ์ •ํ•˜์ง€ ๋ง์ž! ํ•˜๋‚˜์”ฉ ๊ฐ™์ด ์•Œ์•„๋ณผ ๊ฒƒ์ด๋‹ˆ

@EntityListeners(AuditingEntityListener.class) // 1
@MappedSuperClass // 2
@Getter // 3
public BaseEntity {
  @CreatedDate // 4
  @Column(updatable = false) // 5
  private LocalDateTime createdDate;
}

์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค๊ฐ€ JPA ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด Auditing ์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.


์ž! ๋ฌด์Šจ ์ƒˆ๋กœ์šด ์–ด๋…ธํ…Œ์ด์…˜๋“ค์ด ์กด์žฌํ•œ๋‹ค. ํ•˜๋‚˜์”ฉ ์•Œ์•„๋ณด์ž.

  1. @EntityListeners(AuditingEntityListener.class)
  2. @MappedSuperClass
  3. @Getter
  4. @CreatedDate
  5. @Column(updatable = false)

@EntityListeners

@EntityListeners ๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ DB์— ์ ์šฉํ•˜๊ธฐ ์ „, ์ดํ›„์— ์ปค์Šคํ…€ ์ฝœ๋ฐฑ์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

 

@EntityListeners ์˜ ์ธ์ž๋กœ ์ปค์Šคํ…€ ์ฝœ๋ฐฑ์„ ์š”์ฒญํ•  ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋˜๋Š”๋ฐ, Auditing ์„ ์ˆ˜ํ–‰ํ•  ๋•Œ๋Š” JPA ์—์„œ ์ œ๊ณตํ•˜๋Š” AuditingEntityListener.class ๋ฅผ ์ธ์ž๋กœ ๋„˜๊ธฐ๋ฉด ๋œ๋‹ค.

 

 

๊ทธ๋Ÿผ ์œ„์— ๋ณด๋Š”๋ฐ”์™€ ๊ฐ™์ด@PrePersist ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ JPA ์˜ Auditing ๊ธฐ๋Šฅ์„ Spring Data JPA ๊ฐ€ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

@MappedSuperClass

@MappedSuperClass ์€ ์—”ํ‹ฐํ‹ฐ์˜ ๊ณตํ†ต ๋งคํ•‘ ์ •๋ณด๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

 

์ฆ‰, ๋ถ€๋ชจ ํด๋ž˜์Šค(์—”ํ‹ฐํ‹ฐ)์— ํ•„๋“œ๋ฅผ ์„ ์–ธํ•˜๊ณ  ๋‹จ์ˆœํžˆ ์†์„ฑ๋งŒ ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•˜๊ณ ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

 

์šฐ๋ฆฌ๋Š” BaseEntity๋ฅผ ์ƒ์„ฑํ•˜๊ณ  Auditing ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— @MappedSuperClass ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

@CreatedDate

@CreatedDate ์–ด๋…ธํ…Œ์ด์…˜์€ Spring Data JPA์˜ Auditing ์—์„œ ๊ฐ€์žฅ ํฅ๋ฏธ๋กœ์šด ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

 

์‚ฌ์‹ค ์ด ์–ด๋…ธํ…Œ์ด์…˜๋„ Spring Data JPA ์˜ ๊ณ ์œ  ๊ธฐ๋Šฅ์€ ์•„๋‹ˆ๊ณ  Spring Data ์— ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ Spring Data ์—์„œ ์ถ”์ƒํ™” ํ•ด๋†“์€ ๊ฒƒ์ด๋‹ค.

CreatedDate์˜ javadoc ์— ๋‚˜์˜จ ์„ค๋ช…์„ ์ฐธ๊ณ ํ•ด๋ณด์ž.

 

Declares a field as the one representing the date the entity containing the field was created.
๋ฒˆ) ํ•„๋“œ๋ฅผ ํฌํ•จํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ž‘์„ฑ๋œ ๋‚ ์งœ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํ•„๋“œ๋ผ๊ณ  ์„ ์–ธํ•œ๋‹ค.

javadoc ์— ๋‚˜์˜จ ๋‚ด์šฉ์„ ๋ณด๋ฉด ์šฐ๋ฆฌ๊ฐ€ ํ•ด๋‹น ํ•„๋“œ๋ฅผ ์„ ์–ธํ•˜๋ฉด ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ž‘์„ฑ๋œ ๋‚ ์งœ, created ๋œ ๋‚ ์งœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

 

์ด์™€ ๋น„์Šทํ•œ ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ช‡ ๊ฐœ ๋” ์กด์žฌํ•œ๋‹ค.

 

  • CreatedDate
    • ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ, ์ƒ์„ฑํ•˜๋Š” ์‹œ๊ฐ์„ ์ž๋™์œผ๋กœ ์‚ฝ์ž…ํ•ด์ค€๋‹ค.
  • CreatedBy
    • ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ, ์ƒ์„ฑํ•˜๋Š” ์‚ฌ๋žŒ์ด ๋ˆ„๊ตฌ์ธ์ง€ ์ž๋™์œผ๋กœ ์‚ฝ์ž…ํ•ด์ค€๋‹ค.
    • ์ƒ์„ฑํ•˜๋Š” ์ฃผ์ฒด๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ AuditorAware<T> ๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.
      • ์ด๋Š” Spring Security ์™€ ํ•จ๊ป˜ ๋‹ค๋ค„์•ผ ํ•˜๋Š” ๋‚ด์šฉ์ด๋ฏ€๋กœ ์ถ”ํ›„ ์—…๋กœ๋“œ ์˜ˆ์ •
  • LastModifiedDate
    • ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ˆ˜์ •๋  ๋•Œ, ์ˆ˜์ •ํ•˜๋Š” ์‹œ๊ฐ์„ ์ž๋™์œผ๋กœ ์‚ฝ์ž…ํ•ด์ค€๋‹ค.
  • LastModifiedBy
    • ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ˆ˜์ •๋  ๋•Œ, ์ˆ˜์ •ํ•˜๋Š” ์ฃผ์ฒด๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ์ž๋™์œผ๋กœ ์‚ฝ์ž…ํ•ด์ค€๋‹ค.
      • ์ƒ์„ฑํ•˜๋Š” ์ฃผ์ฒด๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด์„œ AuditorAware<T> ๋ฅผ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.
        • ์ด๋Š” Spring Security ์™€ ํ•จ๊ป˜ ๋‹ค๋ค„์•ผ ํ•˜๋Š” ๋‚ด์šฉ์ด๋ฏ€๋กœ ์ถ”ํ›„ ์—…๋กœ๋“œ ์˜ˆ์ •

์œ„์—์„œ ์–ธ๊ธ‰ํ•˜์˜€๋“ฏ CreatedBy ์™€ LastModifiedBy ์–ด๋…ธํ…Œ์ด์…˜์€ ์ถ”ํ›„ Spring Security ์‹œ๋ฆฌ์ฆˆ์™€ ํ•จ๊ป˜ ์ด์•ผ๊ธฐ ํ•ด๋ณด๋ ค ํ•œ๋‹ค.
๋งŒ์•ฝ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด AuditorAware ์ ์šฉ ํ‚ค์›Œ๋“œ๋กœ ๊ฒ€์ƒ‰ํ•ด๋ณผ ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค.

@Column(updatable = false)

์ด๋Š” JPA์˜ ๊ธฐ๋ณธ ์–ด๋…ธํ…Œ์ด์…˜ ์—์„œ ๋‚˜์˜จ @Column๊ณผ ๋™์ผํ•˜๋‹ค.

 

updatable ์„ ์™œ false ๋กœ ํ–ˆ์„๊นŒ?

 

ํ˜น์‹œ ๋ชจ๋ฅผ ์ƒํ™ฉ์„ ๋Œ€๋น„ํ•ด์„œ์ด๋‹ค.

์šฐ๋ฆฌ๋Š” ํ•ด๋‹น BaseEntity๋ฅผ JPA๊ฐ€ ํ…Œ์ด๋ธ”์— ์ ‘๊ทผํ•˜๋Š” ์‹œ์ ์—๋งŒ JPA๊ฐ€ ์‚ฌ์šฉํ•˜๋„๋ก ํ•˜๊ณ  ์‹ถ์€๋ฐ ๋งŒ์•ฝ ๊ฐœ๋ฐœ์ž์— ์˜ํ•ด ์ˆ˜์ •๋˜๋ฉด ์•ˆ๋˜๊ธฐ ๋•Œ๋ฌธ์— updatable์„ false๋กœ ํ•ด์ฃผ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

ํ…Œ์ŠคํŠธ

์ž ์ด์ œ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž.

์šฐ๋ฆฌ๋Š” User ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ƒ์„ฑํ•  ๋•Œ createdBy ์™€ updatedBy ๋ฅผ Auditing ํ•˜๊ฒŒ ํ•  ๊ฒƒ์ด๋‹ค.

  • BaseEntity.class
    • Auditing ์„ ์ˆ˜ํ–‰ํ•  ๊ธฐ๋ณธ ์—”ํ‹ฐํ‹ฐ์ด๋‹ค.
  • User.class
    • BaseEntity ๋ฅผ ์ƒ์†๋ฐ›์•„ BaseEntity์˜ Auditing ์„ ์ด์šฉํ•  ๊ฐ์ฒด์ด๋‹ค.
  • UserRepository.interface
    • ๊ธฐ๋ณธ JpaRepository ๋ฅผ ์ƒ์†๋ฐ›์„ ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.
  • UserRepositoryTest.class
    • ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค์ด๋‹ค.

BaseEntity.class

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(updatable = false)
    private LocalDateTime updatedAt;
}

์šฐ๋ฆฌ์˜ ์†Œ์ค‘ํ•œ Auditor ์—”ํ‹ฐํ‹ฐ์ด๋‹ค.

 

์•ž์„œ ๋ง ํ–ˆ๋˜ 4๊ฐ€์ง€ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

User.class

@Entity
public class User extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String address;
    private int age;
}

์ค‘์š”ํ•œ ๊ฒƒ์€ BaseEntity ๋ฅผ extends ํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.


์—ญ์‹œ ๋กฌ๋ณต ๊ด€๋ จ๋œ ์–ด๋…ธํ…Œ์ด์…˜์€ ์‚ญ์ œํ–ˆ๋‹ค.

UserRepository.interface

public interface UserRepository extends JpaRepository<User, Long> {
}

UserRepositoryTest.class

@SpringBootTest
@Transactional
@Rollback(false)
class UserRepositoryTest {

    @Autowired
    UserRepository userRepository;

    @Test
    @DisplayName("Auditing ๊ธฐ๋Šฅ ์ ์šฉ")
    void findUser() {
        // given
        User user = User.builder()
                .username("user ")
                .age(20)
                .address("Korfea")
                .build();

        // when
        User savedUser = userRepository.save(user);

        // then
        assertNotNull(savedUser.getCreatedAt());
        assertNotNull(savedUser.getUpdatedAt());
    }
}

ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ฟผ๋ฆฌ๋ฌธ๋„ ์ ์ ˆํ•˜๊ฒŒ ๋‚˜๊ฐ€๊ฒŒ ๋˜๊ณ  h2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋„ ์ž˜ ์ ์šฉ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋Œ“๊ธ€