Hibernate Events 로 Entity 변경 감지

Jieun Yang·2025년 12월 15일

CustomPostUpdateEventListener.java


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xforce.admin.model.entity.admin.Policy;
import com.xforce.admin.model.entity.admin.PolicyHistory;
import com.xforce.admin.model.entity.admin.Server;
import com.xforce.admin.model.entity.admin.ServerHistory;
import com.xforce.admin.model.entity.common.AdminUser;
import com.xforce.admin.model.entity.common.AdminUserHistory;
import com.xforce.admin.model.repository.AdminUserHistoryRepository;
import com.xforce.admin.model.repository.PolicyHistoryRepository;
import com.xforce.admin.model.repository.ServerHistoryRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.event.spi.PostUpdateEvent;
import org.hibernate.event.spi.PostUpdateEventListener;
import org.hibernate.persister.entity.EntityPersister;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Slf4j
@Component
@RequiredArgsConstructor
public class CustomPostUpdateEventListener implements PostUpdateEventListener {

    private final AdminUserHistoryRepository auditUserHistoryRepository;
    private final PolicyHistoryRepository policyHistoryRepository;
    private final ServerHistoryRepository serverHistoryRepository;
    private final ObjectMapper objectMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void onPostUpdate(PostUpdateEvent event) {
        Object entity = event.getEntity();

        try {
            if (entity instanceof AdminUser user) {
                handleAdminUser(event, user);
            } else if (entity instanceof Policy policy) {
                handleAdminPolicy(event, policy);
            } else if (entity instanceof Server server) {
                handleAdminServer(event, server);
            }

        } catch (Exception e) {
            log.error("Failed to save History. error: {}", entity.getClass().getSimpleName(), e);
            throw new RuntimeException("History  저장 중 오류 발생", e);
        }
    }

    /**
     * User Update Handler
     */
    private void handleAdminUser(PostUpdateEvent event, AdminUser user) throws Exception {

        // 변경된 필드 찾기
        Set<String> ignoredFields = Set.of("userUpdatedAt", "lastLoginAt", "userUpdatedBy", "passwordUpdatedAt");

        String jsonChanges = getJsonChanges(event, ignoredFields);
        if (jsonChanges == null) return;

        // history 저장
        String reason = user.getChangedReason() != null ? user.getChangedReason() : "Update detected!";

        AdminUserHistory history = AdminUserHistory.builder()
                .userId(user.getUserId())
                .changedFields(jsonChanges)
                .changedReason(reason)
                .changedBy(user.getUserUpdatedBy())
                .build();

        auditUserHistoryRepository.save(history);
    }

    /**
     * Policy Update Handler
     */
    private void handleAdminPolicy(PostUpdateEvent event, Policy policy) throws Exception {

        Set<String> ignoredFields = Set.of(
                "createdAt", "updatedAt", "createdBy", "updatedBy",
                "deployedAt", "deployedBy", "deployedVersion");

        String jsonChanges = getJsonChanges(event, ignoredFields);
        if (jsonChanges == null) return;

        String reason = policy.getChangedReason() != null ? policy.getChangedReason() : "Update detected!";

        PolicyHistory history = PolicyHistory.builder()
                .policyId(policy.getPolicyId())
                .changedFields(jsonChanges)
                .changedReason(reason)
                .changedBy(policy.getUpdatedBy())
                .build();

        policyHistoryRepository.save(history);
    }

    /**
     * Server Update Handler
     */
    private void handleAdminServer(PostUpdateEvent event, Server server) throws Exception {

        Set<String> ignoredFields = Set.of(
                "createdAt", "updatedAt", "createdBy", "updatedBy");

        String jsonChanges = getJsonChanges(event, ignoredFields);
        if (jsonChanges == null) return;

        String reason = server.getChangedReason() != null ? server.getChangedReason() : "Update detected!";

        ServerHistory history = ServerHistory.builder()
                .serverId(server.getServerId())
                .changedFields(jsonChanges)
                .changedReason(reason)
                .changedBy(server.getUpdatedBy())
                .build();

        serverHistoryRepository.save(history);
    }

    // 변경된 필드 찾기 메서드
    private String getJsonChanges(PostUpdateEvent event, Set<String> ignoredFields) throws Exception{
        Map<String, Object> changes = new HashMap<>();
        String[] propertyNames = event.getPersister().getPropertyNames();
        Object[] oldState = event.getOldState();
        Object[] newState = event.getState();
        int[] dirtyProperties = event.getDirtyProperties();

        for (int index : dirtyProperties) {
            String fieldName = propertyNames[index];

            if (ignoredFields.contains(fieldName)) {
                continue;
            }

            // 값 비교 (Null 체크 및 equals)
            Object oldValue = oldState[index];
            Object newValue = newState[index];

            if (oldValue == null && newValue == null) {continue;}
            if (oldValue != null && oldValue.equals(newValue)) {continue;}

            Map<String, Object> fieldChange = new HashMap<>();
            fieldChange.put("old", oldState[index]);
            fieldChange.put("new", newState[index]);
            changes.put(fieldName, fieldChange);
        }

        if (changes.isEmpty()) return null;

        return objectMapper.writeValueAsString(changes);
    }

    @Override
    public boolean requiresPostCommitHandling(EntityPersister entityPersister) {
        return false; // 트랜잭션 커밋 전/후 언제 실행할지 설정 (false: 커밋 전 즉시 실행)
    }

HibernateListener.java

@Component
@RequiredArgsConstructor
@Slf4j
public class HibernateListener {

    private final EntityManagerFactory  entityManagerFactory;
    private final CustomPostUpdateEventListener customPostUpdateEventListener;

    @PostConstruct
    public void init() {
        log.info("Initializing HibernateListener");
        SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);

        // 이벤트에 리스너 등록
        registry.getEventListenerGroup(EventType.POST_UPDATE).appendListener(customPostUpdateEventListener);
    }
}

0개의 댓글