[λ°°μ보μ Spring Data JPA] JPA Auditing κΈ°λ₯μ μ¬μ©ν΄μ μμ±, μμ μΌμ μλννκΈ°

ν΄λΉ κΈμ λ°°μ보μ Spring Data JPA μλ¦¬μ¦ μ λλ€.
ν΄λΉ μ리μ¦μ λ΄μ©μ΄ μ΄μ΄μ§λ ννμ΄λ―λ‘ κΈμ λ΄μ© μ€μ μλ΅λλ λ§λ€μ΄ μμ μ μμΌλ, μμΈν μ¬νμ μλ λ§ν¬λ₯Ό μ°Έκ³ ν΄μ£ΌμΈμ!
- Spring Data JPAμ κΈ°λ³Έκ³Ό νλ‘μ νΈ μμ± :: μλ¦¬μ¦ νμ΅ νκ²½ μ€λΉ
- JPAμ κΈ°λ³Έκ³Ό Spring Data JPA
- Springboot project μμ JPA μ€μ νκΈ°
- JPAμ κΈ°λ³Έ μ΄λ
Έν
μ΄μ
λ€ :: JPAμ μμκ³Ό λμμ λ
- μν°ν°μ ν μ΄λΈ λ§€ν
- νλμ μ»¬λΌ λ§€ν
- λ§€ν ν
μ΄λΈκ³Ό μ°κ΄κ΄κ³ λ§€ννκΈ° :: RDBμ κ½, μ°κ΄ κ΄κ³
- μ°κ΄κ΄κ³λ?, μΈλ ν€λ?, λ§€ν ν μ΄λΈμ΄λ?
- μΌλμΌ, μΌλλ€, λ€λμΌ μ°κ΄κ΄κ³
- κ³΅ν΅ μΈν°νμ΄μ€ κΈ°λ₯ :: μ΄λ»κ² Data JPA λ λμν κΉ?
- λ¨κ±΄ μ‘°ν λ°ν νμ
- 컬λ μ μ‘°ν λ°ν νμ
- μ¬μ©μ μ μ 쿼리 μ΄μ©νλ λ°©λ² :: λ΄κ° μνλ 쿼리λ₯Ό JPA μμ λ§λ€μ΄λ³΄μ!
- λ©μλ μ΄λ¦μΌλ‘ 쿼리 μμ±νκΈ°
@Queryλ₯Ό μ΄μ©νμ¬ λ©μλμ μ μ 쿼리 μμ±νκΈ°
- νμ΄μ§κ³Ό μ λ ¬ :: κ²μνκ³Ό κ°μ νμ΄μ§κ° μλ μλΉμ€μμ λΉμ λ°νλ JPA νμ΄μ§!
- Data JPAμ νμ΄μ§κ³Ό μ λ ¬
- Web MVC μμ JPA νμ΄μ§κ³Ό μ λ ¬
- Auditing :: λͺ¨λ μμ²κ³Ό μλ΅μ
λκ°, μΈμ μ κ·Όνλμ§ νλμ μν°ν°λ‘ κ΄λ¦¬νμ.- μμ JPAμ Auditing
- Spring Data JPAμ Auditing
μλΉμ€λ₯Ό μ΄μν λ μ¬μ©μμ κΈ°λ³Έμ μΈ λ‘κ·Έλ₯Ό 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 μ μννμ¬ κ°μ μ λ°μ΄νΈ νλ€.
μ! λ¬΄μ¨ μλ‘μ΄ μ΄λ
Έν
μ΄μ
λ€μ΄ μ‘΄μ¬νλ€. νλμ© μμ보μ.
@EntityListeners(AuditingEntityListener.class)@MappedSuperClass@Getter@CreatedDate@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 λ°μ΄ν°λ² μ΄μ€μλ μ μ μ©λ κ²μ λ³Ό μ μλ€.
