개발일지

[개발일지_safeHome] 6.JPA Audit 설정 추가

woopii 2025. 12. 15. 00:43

1. BaseEntity에 Audit관련 설정 추가하면서 수정

생성, 수정등의 Jpa Audit 자동 관리를 추가함으로서 책임분리에 충실하고자 함 

 

package com.woopi.safehome.global.`object`

import jakarta.persistence.*
import org.springframework.data.annotation.CreatedBy
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedBy
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long? = null
        protected set

    @Column(name = "is_deleted", nullable = false)
    var isDeleted: Boolean = false
        protected set

    @CreatedBy
    @Column(name = "created_id", nullable = false, updatable = false)
    var createdId: Long? = null
        protected set

    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    var createdAt: LocalDateTime? = null
        protected set

    @LastModifiedBy
    @Column(name = "updated_id")
    var updatedId: Long? = null
        protected set

    @LastModifiedDate
    @Column(name = "updated_at")
    var updatedAt: LocalDateTime? = null
        protected set

    fun delete() {
        this.isDeleted = true
    }

}

 

 

2. JpaAuditConfig 설정 추가 (EnableJpaAuditing 설정, AuditorAware 구현체)

Jpa Audit을 사용할려면 다음과 같이 config와 구현체를 만들어야 함

 

  • JpaAuditConfig 
package com.woopi.safehome.global.config

import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaAuditing

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
class JpaAuditConfig {
}

 

  • AuditAwareImpl
package com.woopi.safehome.global.config.impl

import org.springframework.data.domain.AuditorAware
import org.springframework.stereotype.Component
import java.util.Optional

@Component("auditorAware")
class AuditAwareImpl: AuditorAware<Long> {

    override fun getCurrentAuditor(): Optional<Long> {
        return Optional.of(1L)
    }

}

 

 

 

3. audit 잘 되는지 테스트

kotest 사용할 거라서 다음의 의존성 추가함

kotest를 사용하는 이유는, 테스트를 행위로 표시하여 의도한 바가 명확함, 그리고 가독성이 좋음(assertion 등이 명확함)

 

	// kotest
	testImplementation("io.kotest:kotest-runner-junit5")
	testImplementation("io.kotest:kotest-assertions-core")
	testImplementation("io.kotest.extensions:kotest-extensions-spring")

 

 

그리고 테스트 전 과연 mock 을 써야하는가에 대한 고민을 좀 했는데,

아무래도 mock은 내가 컨트롤 할 수 없는, 아래와 같은 '외부 호출' 같은 경우에만 사용하는게 맞는거 같다. 

  • 다른 서버
  • 메시지 시스템
  • 메일 / SMS
  • 결제, 인증

이런 외부 호출 시 이럴것이다 라는 시나리오 가정을 하고,

이후 내 코드에서의 동작은 실제 동작을 확인해야하기 때문에 mock으로 하면 안될거 같다.

 

암튼 audit에 관련된 테스트를 하나 만들면 다음과 같을 수 있다.

 

package com.woopi.safehome.domain._sample.adapter.outbound.persistence.jpa

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.comparables.shouldBeGreaterThan
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest
class SampleEntityAuditTest(
    @Autowired
    private val sampleRepository: SampleRepository
) : BehaviorSpec({

    Given("SampleEntity를 생성할 때") {

        When("처음 저장하면") {
            val entity = SampleEntity(
                name = "sample",
                code = "S001"
            )

            val saved = sampleRepository.save(entity)

            Then("created audit 정보가 자동으로 채워진다") {
                saved.id.shouldNotBeNull()

                saved.createdId shouldBe 1L
                saved.createdAt.shouldNotBeNull()

                saved.updatedId shouldBe 1L
                saved.updatedAt.shouldNotBeNull()
            }

            When("값을 수정해서 다시 저장하면") {
                val beforeUpdatedAt = saved.updatedAt

                saved.changeName("sample-modified")
                val updated = sampleRepository.save(saved)

                Then("updated audit 정보가 갱신된다") {
                    updated.updatedId shouldBe 1L
                    updated.updatedAt.shouldNotBeNull()
                    updated.updatedAt!! shouldBeGreaterThan  beforeUpdatedAt!!
                }
            }
        }
    }
})

 

 

해당 테스트는 audit이 잘 되는지만 보려는 단위 테스트여서 강제로 createdId, updatedId를 1로 고정했는데,
나중에 이부분은 개선할 예정이다