JPA/Heernate Persistent-Context事件及扩展

背景

监听Hibeernate的Entity实体变化

Event Core

众所周知,Jpa想触发Hibernate Event就必须是Persistent持有的实体。delete操作会先通过Id查询,生成DeleteAction操作放入ActionQueue;deleteAll生成多个DeleteAction放入ActionQueue。这样批量操作效率低因此引入

@NoRepositoryBean
public interface CommonRepository<T,ID>  {
    // method1
    void deleteAllByIdIn(List<ID> oids) ;
    // method2
    @Query("delete from xx where id in (:ids)")
    void deleteAllByIdIn02(List<ID> ids) ;

}

方式1与方式2有区别吗?
答案是肯定的。第一种方式依旧是通过ActionQueue触发,且含有post_delete event,Query方式不支持触发。
这里会引入另一个问题——JPA先删后插(主键依赖数据库自增,不是序列,有以为冲突)

  1. 用JPA默认方式
// others
repository.delete(entity)
repository.save(entity);
// others

DeleteAction放入ActionQueue,当执行InsertAction发现依赖数据库主键触发isEarlyInsert直接生成插入sql,此时会报唯一主键冲突.
2. 用自定义删除

repository.deleteAllByIdIn02(entity)
repository.save(entity);

直接生成删除sql,再插入sql。 perfect! 但是不能触发event事件,因此我们要手动发出事件,且根据ID删除

监听Hibernate的集中事件

@Configuration
@Slf4j
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class EntityChangedConfiguration {

    @PersistenceUnit(unitName = "defaultEntityManagerFactory")
    private EntityManagerFactory emf;

    @Autowired
    private EntityChangedListener entityChangedListener;

    Set<String> ignoreProperties = new HashSet<>();


    @Bean
    public HibernatePropertiesCustomizer configureStatementInspector() {
        return (properties) -> properties.put(AvailableSettings.STATEMENT_INSPECTOR, (StatementInspector) sql -> {
            log.warn(sql);
            return sql;
        });
    }

    @PostConstruct
    protected void init() {
        SessionFactoryImpl sessionFactory = emf.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
        registry.getEventListenerGroup(EventType.POST_UPDATE).appendListener(entityChangedListener);
        registry.getEventListenerGroup(EventType.POST_DELETE).appendListener(entityChangedListener);
        registry.getEventListenerGroup(EventType.POST_INSERT).appendListener(entityChangedListener);

        SessionFactoryOptions sessionFactoryOptions = sessionFactory.getSessionFactoryOptions();
        Field field = ReflectionUtils.findField(SessionFactoryOptionsBuilder.class, "interceptor");
        ReflectionUtils.makeAccessible(field);
        CustomInterceptor customInterceptor = new CustomInterceptor();
        ReflectionUtils.setField(field, sessionFactoryOptions, customInterceptor);

        // statementInspector可以打印preparestatement sql语句
        // Field field2 = ReflectionUtils.findField(SessionFactoryOptionsBuilder.class, "statementInspector");
        // ReflectionUtils.makeAccessible(field2);
        // ReflectionUtils.setField(field2,sessionFactoryOptions, customInterceptor);

        // init ignore property
        setupIgnored();
    }

    private void setupIgnored() {
        // only this property be updated
        ignoreProperties.add("updateDatetime");
        ignoreProperties.add("updateId");
        ignoreProperties.add("hVersion");
    }

    /**
     * Monitor Entity
     */
    @Component
    public class EntityChangedListener implements PostUpdateEventListener, PostDeleteEventListener, PostInsertEventListener {
        @Override
        public void onPostUpdate(PostUpdateEvent event) {
            String[] properties = event.getPersister().getPropertyNames();
            Object[] state = event.getState();
            Object[] oldState = event.getOldState();
            Arrays.stream(event.getDirtyProperties()).filter(idx -> !ignoreProperties.contains(properties[idx])).findFirst().ifPresent((ele) -> {
                //  It will record changes even for other fields besides those specified in ignoreProperties
                List<OperationLogDetailMessageBO> updateData = Arrays.stream(event.getDirtyProperties())
                        .mapToObj(idx -> new OperationLogDetailMessageBO(String.valueOf(oldState[idx]), String.valueOf(state[idx]), event.getEntity().getClass().getSimpleName() + "#" + properties[idx], OperationLogHandler.UPDATE_OP))
                        .collect(Collectors.toList());
                OperationLogHandler.recordChangedToContext(updateData, OperationLogHandler.UPDATE_OP);
            });
        }

        @Override
        public boolean requiresPostCommitHanding(EntityPersister persister) {
            return false;
        }

        @Override
        public void onPostDelete(PostDeleteEvent event) {
            Object entity = event.getEntity();
            String str = JSONObject.toJSONString(entity);
            List<OperationLogDetailMessageBO> deleteData = Collections.singletonList(new OperationLogDetailMessageBO(str, null, event.getEntity().getClass().getSimpleName(), OperationLogHandler.DELETE_OP));
            OperationLogHandler.recordChangedToContext(deleteData, OperationLogHandler.DELETE_OP);
        }

        @Override
        public void onPostInsert(PostInsertEvent event) {
            Object entity = event.getEntity();
            String str = JSONObject.toJSONString(entity);
            List<OperationLogDetailMessageBO> insertData = Collections.singletonList(new OperationLogDetailMessageBO(null, str, event.getEntity().getClass().getSimpleName(), OperationLogHandler.INSERT_OP));
            OperationLogHandler.recordChangedToContext(insertData, OperationLogHandler.INSERT_OP);
        }

    }

    /**
     * When the transaction is committed, stat with handle operation log.
     */
    @Aspect
    @Component
    public class LogHandleAspect {

        private final TransactionSynchronizationAdapter afterCommitLogic = new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                OperationLogHandler.handle();
            }
        };

        @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
        public void transactionalMethods() {
        }

        @AfterReturning("transactionalMethods()")
        public void afterTransactionCommit() {
            List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
            boolean b = TransactionSynchronizationManager.hasResource(LogHandleAspect.class.toString());
            if (synchronizations.contains(afterCommitLogic)) {
                log.warn("transactionalMethods has existed ");
                return;
            }
            log.warn("transactionalMethods registering");
            TransactionSynchronizationManager.registerSynchronization(afterCommitLogic);
            // TransactionSynchronizationManager.bindResource(LogHandleAspect.class.toString(), afterCommitLogic);
        }

    }


    /**下面代码忽略*/
    @Bean
    public Interceptor customInterceptor() {
        return new CustomInterceptor(); // 注册自定义的拦截器
    }

   
    public static class CustomInterceptor extends EmptyInterceptor {

        @Override
        public String onPrepareStatement(String sql) {
            // 拦截 SQL 语句的执行
            if(StringUtils.containsIgnoreCase(sql, "delete")){
                System.out.println(sql);
            };
            return super.onPrepareStatement(sql);
        }

        @Override
        public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
            return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
        }

        @Override
        public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
            super.onDelete(entity, id, state, propertyNames, types);
        }

        @Override
        public void postFlush(Iterator entities) {
            List<Object> list = new ArrayList<>();
            entities.forEachRemaining(list::add);

            super.postFlush(entities);
        }

        @Override
        public void afterTransactionCompletion(Transaction tx) {
            super.afterTransactionCompletion(tx);
        }
    }

收集change与业务有关

@Slf4j
public class OperationLogHandler {
    final static String DELETE_OP = "deleteOp";
    final static String UPDATE_OP = "updateOp";
    final static String INSERT_OP = "insertOp";
    static final int SCOPE = RequestAttributes.SCOPE_REQUEST;
    private static final String OPERATION_MODULE = "xxx";


    public static void handle() {
        log.warn("Operation Log handle starting.");
        List<OperationLogDetailMessageBO> changedData = new ArrayList<>();

        // 1. delete data
        List<OperationLogDetailMessageBO> delete = removeChangedFromContext(DELETE_OP);
        if (!CollectionUtils.isEmpty(delete)) {
            changedData.addAll(delete);
        }
        // 2. update data
        List<OperationLogDetailMessageBO> update = removeChangedFromContext(UPDATE_OP);
        if (!CollectionUtils.isEmpty(update)) {
            changedData.addAll(update);
        }
        // 3. insert data
        List<OperationLogDetailMessageBO> insert = removeChangedFromContext(INSERT_OP);
        if (!CollectionUtils.isEmpty(insert)) {
            changedData.addAll(insert);
        }
        if (CollectionUtils.isEmpty(changedData)) {
            return;
        }
        // 4. assembly
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) getRequestAttributes();
        // 5. skip logging logic if was not a request
        if (requestAttributes == null) {
            return;
        }
        HttpServletRequest request = requestAttributes.getRequest();
        BizLogReflectBO boPKinfo = getBizLogReflectBO(request);
        OperationLogMessageBO operationLogMessageBo = createOperationLogMessageBo(new OperationResult(), boPKinfo, 1, new Date(), changedData, request.getMethod(), OPERATION_MODULE);
        OperationClient operationClient = SpringContext.getBean(OperationClient.class);
        JsonEntity<Boolean> logSavedResult = operationClient.saveBizLog(operationLogMessageBo);
        log.info("Operation Log handle result {}.", JSONObject.toJSONString(logSavedResult));
    }


    /**assembly BO
     * @param request**/
    private static BizLogReflectBO getBizLogReflectBO(HttpServletRequest request) {
        // 1. request URI as target_value1
        String path = request.getRequestURI();
        path = subStringLast(path, 64);

        // 2. read request parameter/body as target_value
        BizLogReflectBO boPKinfo = new BizLogReflectBO();
        boPKinfo.setTargetType("api-request");
        boPKinfo.setTargetValue1(path);
        boPKinfo.setTargetValue2(StringUtil.subString(JSONObject.toJSONString(request.getParameterMap()), 255));
        boPKinfo.setTargetValue3(StringUtil.subString(JSONObject.toJSONString(JSONObject.parse(MDC.get(MDCVariables.STELLR_REQUEST_BODY))), 255));
        return boPKinfo;
    }

    /**
     * 组装LOG
     */
    private static OperationLogMessageBO createOperationLogMessageBo(OperationResult opResult, BizLogReflectBO boPKinfo, Integer change, Date startTime, List<OperationLogDetailMessageBO> detailList, String operationType, String operationModule) {
        EntryInfoBO entryInfo = OperationUtil.getEntryInfo(opResult);
        if (StringUtil.isEmpty(opResult.getRequestId())) {
            HttpServletRequest request = WebUtil.getCurrentRequest();
            if (null != request) {
                opResult.setRequestId(WebUtil.getRequestId(request));
            }
        }

        String appName = SpringContext.getProperty("spring.application.name");
        if (StringUtil.isEmpty(appName)) {
            appName = SpringContext.getApplicationContext().getApplicationName().replace("/", "");
        }
        if (null == opResult.getUserNo() || StringUtils.isEmpty(opResult.getUserType())) {
            OperationUtil.setResellerOrCustomerInfo(opResult);
        }
        opResult.setDataChange(true);
        opResult.setStatus("Success");
        if ("Failed".equalsIgnoreCase(opResult.getStatus())) {
            change = 0;
        }

        if (opResult.getDataChange() != null) {
            change = opResult.getDataChange() ? 1 : 0;
        }
        OperationLogMessageBO message = new OperationLogMessageBO(opResult.getRequestId(), SpringContext.getSystemCompanyName(), appName, opResult.getUserNo(), opResult.getUserType(), operationModule, opResult.getBatchId(), operationType, boPKinfo.getTargetType(), boPKinfo.getTargetValue1(), boPKinfo.getTargetValue2(), boPKinfo.getTargetValue3(), opResult.getReserveValue1(), opResult.getReserveValue2(), opResult.getReserveValue3(), opResult.getI18nKey(), ObjectUtil.toJsonSilently(opResult.getI18nKeyParams()), opResult.getStatus(), change, entryInfo.getEntryId(), new Date(), entryInfo.getEntryUserName(), entryInfo.getEntryUserType(), startTime, DateUtil.now(), detailList);
        interceptOverlongValues(message);
        return message;
    }

    private static void interceptOverlongValues(OperationLogMessageBO message) {
        message.setReserveValue1(StringUtil.subString(message.getReserveValue1(), 255));
        message.setReserveValue2(StringUtil.subString(message.getReserveValue2(), 255));
        message.setReserveValue3(StringUtil.subString(message.getReserveValue3(), 255));
        Optional.ofNullable(message.getDetailList()).ifPresent((details) -> {
            for (OperationLogDetailMessageBO detail : details) {
                // 缩写
                detail.setChangeFieldName(compactStr(detail.getChangeFieldName(), 32));
                detail.setBeforeValue(StringUtil.subString(detail.getBeforeValue(), 500));
                detail.setAfterValue(StringUtil.subString(detail.getAfterValue(), 500));
            }
        });
    }

    public static void main(String[] args) {
        String originalString = "SpecificationAttribute#updateDatetime";
        String abbreviation = getAbbreviation(originalString);
        System.out.println("Abbreviation: " + abbreviation);
    }

    /**
     * HelloWorld
     * @param str HelloWorld
     * @return HW
     */
    private static String getAbbreviation(String str) {


        StringBuilder abbreviation = new StringBuilder();
        String[] words = str.split("(?=[A-Z])|^[a-z]"); // 使用正则表达式来匹配非空格的大写字母或者空格分隔的单词
        for (String word : words) {
            abbreviation.append(word.charAt(0)); // 获取每个单词的首字母
        }
        return abbreviation.toString();
    }

    private static String compactStr(String str, int maxLen) {
        if (str.length() <= maxLen) {
            return str;
        }
        String[] split = str.split("#");
        String afterEntityName = getAbbreviation(split[0]);
        if (split.length != 2) {
            return afterEntityName;
        }
        String afterFieldName = "#" + split[1];
        if (afterEntityName.length() + afterFieldName.length() > maxLen) {
            afterFieldName = getAbbreviation(split[1]);
        }
        return afterEntityName + afterFieldName;
    }

    private static String subStringLast(String str, int maxLen) {
        if (str == null || maxLen <= 0 || str.length() <= maxLen) {
            return str;
        }

        return str.substring(str.length() - maxLen);
    }

    /**Temporary record logs to the request context*/
    static <T> void recordChangedToContext(List<T> data, String changeType) {
        RequestAttributes requestAttributes = getRequestAttributes();
        if (requestAttributes == null) {
            return;
        }
        String key = getThreadSafeKey(changeType);
        List<T> container = (List<T>) requestAttributes.getAttribute(key, SCOPE);
        if (container == null) {
            container = new ArrayList<>();
            requestAttributes.setAttribute(key, container, SCOPE);
        }
        container.addAll(data);
    }

    static <T> List<T> removeChangedFromContext(String changeType) {
        RequestAttributes requestAttributes = getRequestAttributes();
        if (requestAttributes == null) {
            return null;
        }

        String key = getThreadSafeKey(changeType);
        List<T> container = (List<T>) requestAttributes.getAttribute(key, SCOPE);
        // must clean
        requestAttributes.removeAttribute(key, SCOPE);
        return container;
    }

    private static String getThreadSafeKey(String changeType) {
        // The purpose of joing thread ID to key is to avoid accessing the same key in multiple threads
        return Thread.currentThread().getId() + ":" + changeType;
    }

    private static RequestAttributes getRequestAttributes() {
        return RequestContextHolder.getRequestAttributes();
    }


}
## 线程池增强,传递上下文Spring线程池

@Slf4j
public class ContextTaskDecorator implements TaskDecorator {
@Override
@Nonnull
public Runnable decorate(@Nonnull Runnable runnable) {

    return handleRunnable(runnable);
}

public Runnable handleRunnable(Runnable runnable) {
    ServletAttributesDecoratorEntity servletAttributesDecoratorEntity = preHandleRunnable();
    return () -> {
        try {
            long start = System.currentTimeMillis();
            // inherit context from parent
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                RequestContextHolder.setRequestAttributes(servletAttributesDecoratorEntity.getRequestAttributes());
            }
            MDC.setContextMap(servletAttributesDecoratorEntity.getMdcContextMap());
            // run task
            if (runnable != null) {
                runnable.run();
            }
            log.info("Thread {} execute complete, cast {} ms.", Thread.currentThread().getName(), System.currentTimeMillis() - start);
        } catch (Exception e) {
            log.error("Thread {} occurred error, message is {}", Thread.currentThread().getName(), e.getMessage());
        } finally {
            RequestContextHolder.resetRequestAttributes();
            MDC.clear();
        }
    };
}

ServletAttributesDecoratorEntity preHandleRunnable() {
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    Map<String, String> copyOfContextMap = MDC.getCopyOfContextMap();
    return new ServletAttributesDecoratorEntity(copyOfContextMap, requestAttributes);
}

/**
 * Used by {@link ContextTaskDecorator}, a simple pojo.
 */
@Data
@AllArgsConstructor
static class ServletAttributesDecoratorEntity {
    private Map<String, String> mdcContextMap;
    // Get from spring mvc DispatcherServlet.
    private RequestAttributes requestAttributes;
}

}

线程池配置

  @Bean("bizExecutor")
    public Executor bizExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(1000);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        executor.setTaskDecorator(new ContextTaskDecorator());
        return executor;
    }

自定义触发CommonRepositoryImpl以触发Event,替换·SimpleJpaRepository·

@NoRepositoryBean
public interface CommonRepository<T,ID>  {


    void deleteAllByOidIn(List<ID> oids) ;

    List<T> findAllByProductIds(List<Integer> productIds);
}

@Transactional
public class CommonRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements CommonRepository<T, ID> {
    private EntityManager em;
    private EventListenerGroup<PostDeleteEventListener> eventListenerGroup;

    public CommonRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager em) {
        super(entityInformation, em);
        this.em = em;
        this.eventListenerGroup = this.getListener(em);
    }

    public CommonRepositoryImpl(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
        this.eventListenerGroup = this.getListener(em);
    }

    private EventListenerGroup<PostDeleteEventListener> getListener(EntityManager em) {
        EntityManagerFactory entityManagerFactory = em.getEntityManagerFactory();
        SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
        EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
        return registry.getEventListenerGroup(EventType.POST_DELETE);
    }

    @Override
    public void deleteInBatch(Iterable<T> entities) {
        super.deleteInBatch(entities);
        entities.forEach(
                entity -> eventListenerGroup.fireEventOnEachListener(new PostDeleteEvent(entity, -1, new Object[0], null, (EventSource) null), PostDeleteEventListener::onPostDelete)
        );

    }


    @Override
    public void deleteAllByOidIn(List<ID> oids) {
           this.deleteInBatch(super.findAllById(oids));
    }

    @Override
    public List<T> findAllByProductIds(List<Integer> productIds) {
        // SimpleJpaRepository simpleJpaRepository = (SimpleJpaRepository)ExposeInvocationInterceptor.currentInvocation().getThis(); 
        // entityInformation from this
        JpaEntityMetadata<?> entityInformation = (JpaEntityMetadata<?>) SimpleJpaRepositoryUtil.getEntityInformation();
        String entityName = entityInformation.getEntityName();

        TypedQuery<T> namedQuery = (TypedQuery<T>) em.createQuery("from " + entityName + " where productId in (:productIds) and countryCode = :countryCode", entityInformation.getJavaType());
        namedQuery.setParameter("productIds", productIds);
        namedQuery.setParameter("countryCode", Context.getCountryCode);
        return namedQuery.getResultList();
    }
}
posted @ 2023-11-28 09:46  小冲冲|  阅读(79)  评论(0)    收藏  举报