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先删后插(主键依赖数据库自增,不是序列,有以为冲突)
- 用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();
}
}

浙公网安备 33010602011771号