解决jpa更新是会插入空值问题(spring boot 3.0)

news/2024/10/30 19:25:50/

首先找到Hibernate 更新操作的事件监听器DefaultMergeEventListener

直接找到合并函数

public void onMerge(MergeEvent event) throws HibernateException {EntityCopyObserver entityCopyObserver = this.createEntityCopyObserver(event.getSession().getFactory());MergeContext mergeContext = new MergeContext(event.getSession(), entityCopyObserver);try {this.onMerge(event, mergeContext);entityCopyObserver.topLevelMergeComplete(event.getSession());} finally {entityCopyObserver.clear();mergeContext.clear();}}

找到更新操作的代码

switch(entityState) {case DETACHED:this.entityIsDetached(event, copiedAlready);break;case TRANSIENT:this.entityIsTransient(event, copiedAlready);break;case PERSISTENT:this.entityIsPersistent(event, copiedAlready);break;default:throw new ObjectDeletedException("deleted instance passed to merge", (Object)null, EventUtil.getLoggableName(event.getEntityName(), entity));
}

一般我们需要修改的是脱管态的代码,也就是这一段

case DETACHED:this.entityIsDetached(event, copiedAlready);break;

点进去看,会发现,做属性比较的函数是copyValues

直接重写该函数


import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
import org.hibernate.engine.spi.*;
import org.hibernate.event.internal.DefaultMergeEventListener;
import org.hibernate.event.spi.MergeContext;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
import org.hibernate.type.Type;
import org.hibernate.type.TypeHelper;public class IgnoreNullEventListener extends DefaultMergeEventListener {public static final IgnoreNullEventListener INSTANCE = new IgnoreNullEventListener();@Overrideprotected void copyValues(EntityPersister persister, Object entity, Object target, SessionImplementor source, MergeContext copyCache) {if (entity == target) {TypeHelper.replace(persister, entity, source, entity, copyCache);} else {Object[] original = persister.getValues(entity);Object[] targets = persister.getValues(target);Type[] types = persister.getPropertyTypes();Object[] copied = new Object[original.length];for (int i = 0; i < types.length; i++) {if (original[i] == null ||original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY ||original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN) {copied[i] = targets[i];} else {copied[i] = types[i].replace(original[i], targets[i], source, target, copyCache);}}persister.setValues(target, copied);}}
}

再配置该监听器


import jakarta.annotation.PostConstruct;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceUnit;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.internal.SessionFactoryImpl;
import org.springframework.context.annotation.Configuration;@Configuration
public class HibernateListenerConfigurer {@PersistenceUnitprivate EntityManagerFactory emf;@PostConstructprotected void init() {SessionFactoryImpl sessionFactory = emf.unwrap(SessionFactoryImpl.class);EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);registry.getEventListenerGroup(EventType.MERGE).clearListeners();registry.getEventListenerGroup(EventType.MERGE).prependListener(IgnoreNullEventListener.INSTANCE);}
}

重启,问题解决

另外,之前考虑过是不是要去掉jpa更新是要查询一次的操作,以下是GPT的回答

JPA 在执行更新操作时,需要在更新之前查询数据库,以获取实体对象的当前持久化状态和版本号等信息,同时检查是否有其他事务对该实体对象进行了修改。如果 JPA 检测到实体对象在更新前被其他事务修改过,那么会抛出 OptimisticLockException 异常,提示应用程序立即停止当前操作,以避免数据冲突和数据不一致等问题。

在 JPA 中,执行更新前首先查询数据库的逻辑,是为了确保数据的一致性和事务的可重复读。如果没有这个查询逻辑,JPA 在执行更新操作时将无法检测到同时对同一个实体对象进行的并发修改,可能会导致数据的不一致。

另外,JPA 中的更新操作是基于实体对象的状态变更识别的,需要将实体对象的状态从 Detached 状态转换为 Managed 状态,然后将其保存到数据库中。在保存之前,JPA 需要重新查询数据库获取实体对象的持久化状态,并将实体对象与数据库的记录进行比较,生成对应的 SQL 语句,以确保数据的一致性和完整性。

因此,JPA 在执行更新操作时需要重新查询数据库,是为了保证数据的一致性和事务的可重复读。不过,如果数据量较大或并发请求过多,这种查询逻辑会极大地影响系统的性能,因此建议根据具体业务场景采取适当的优化措施。

然后出于谨慎考虑,我并没有去掉这个机制,如果有需要的话,需要重写entityIsDetached函数,具体影响代码如下:

 protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {LOG.trace("Merging detached instance");Object entity = event.getEntity();EventSource source = event.getSession();EntityPersister persister = source.getEntityPersister(event.getEntityName(), entity);String entityName = persister.getEntityName();Object id = event.getRequestedId();Object clonedIdentifier;if (id == null) {id = persister.getIdentifier(entity, source);} else {clonedIdentifier = persister.getIdentifier(entity, source);if (!persister.getIdentifierType().isEqual(id, clonedIdentifier, source.getFactory())) {throw new HibernateException("merge requested with id not matching id of passed entity");}}clonedIdentifier = persister.getIdentifierType().deepCopy(id, source.getFactory());Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(CascadingFetchProfile.MERGE, () -> {return source.get(entityName, clonedIdentifier);});if (result == null) {this.entityIsTransient(event, copyCache);} else {copyCache.put(entity, result, true);Object target = this.unproxyManagedForDetachedMerging(entity, result, persister, source);if (target == entity) {throw new AssertionFailure("entity was not detached");}if (!source.getEntityName(target).equals(entityName)) {throw new WrongClassException("class of the given object did not match class of persistent copy", event.getRequestedId(), entityName);}if (this.isVersionChanged(entity, source, persister, target)) {StatisticsImplementor statistics = source.getFactory().getStatistics();if (statistics.isStatisticsEnabled()) {statistics.optimisticFailure(entityName);}throw new StaleObjectStateException(entityName, id);}this.cascadeOnMerge(source, persister, entity, copyCache);this.copyValues(persister, entity, target, source, copyCache);this.markInterceptorDirty(entity, target);event.setResult(result);}}

查询元数据的代码为

Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(CascadingFetchProfile.MERGE, () -> {return source.get(entityName, clonedIdentifier);});

将其改为自己新建的对象即可,注意,主键要与写入的数据一致


http://www.ppmy.cn/news/279676.html

相关文章

Tomcat启动闪退的详细解决方法(捕获的野生的java1.8.0_321和野生的Tomcat8实验)

1.实验说明 本实验将采用捕获的野生的java1.8.0_321和野生的Tomcat8进行实验。而且不需要安装服务。 2.配置声明&#xff1a; java -version javac -version CATALINA_HOME 说明&#xff1a;CATALINA_HOME配置到放置到tomcat的目录 Path 说明&#xff1a;Path路径配置到tomca…

大模型核心技术原理: Transformer架构详解

在大模型发展历程中&#xff0c;有两个比较重要点&#xff1a;第一&#xff0c;Transformer 架构。它是模型的底座&#xff0c;但 Transformer 不等于大模型&#xff0c;但大模型的架构可以基于 Transformer&#xff1b;第二&#xff0c;GPT。严格意义上讲&#xff0c;GPT 可能…

HKDF(基于HMAC的密钥导出函数)

文章目录 HKDF(基于HMAC的密钥导出函数)1. 基础2. 什么是HKDFHKDF-Extracthkdf.Expand()原理Go语言的crypto/hkdf包hkdf示例 3. Argon2id4. 参考 HKDF(基于HMAC的密钥导出函数) 1. 基础 HKDF叫HMAC-based KDF(key derivation function)&#xff0c;基于HMAC的密钥推导函数。 …

FreeRTOS学习笔记(五)——应用开发(三)

文章目录 0x01 软件定时器应用场景定时器精度运作机制软件定时器控制模块函数接口xTimerCreate()prvInitialiseNewTimer()xTimerStart()xTimerGenericCommand()xTimerStartFromISR()xTimerStop()xTimerStopFromISR()xTimerDelete()软件定时器任务创建以及执行原理软件定时器实验…

金立e3t刷android4.4,金立E3T 刷机包 百度云完美版

【出品】&#xff1a;812601591 【特性】 1. 安全稳定&#xff1a;基于百度云最新版rom精简优化&#xff0c;稳定运行&#xff01; 2. 省电耐用&#xff1a;添加独有省电逻辑&#xff0c;待机时长优于官方&#xff01; 3. 深度精简&#xff1a;剔除大量冗余的官方应用及多余插件…

金立手机创始人:山寨手机商两年后或只剩20家

CFP供图 “如果说原来有一千家山寨机&#xff0c;现在应该只有不到两百家&#xff0c;再过一两年&#xff0c;至少再去个零。”作为金立手机创始人兼董事长&#xff0c;刘立荣同时也是深圳手机行业协会会长&#xff0c;长期扎根于深圳这片全球手机产业的热土。在他看来&#xf…

站在悬崖边上,金立手机的生死较量

我们做个试验吧&#xff01; 能一天不使用手机的请扣1 能一个星期不使用手机的请扣2 能一个月不使用手机的请扣3 能一年不使用手机的&#xff0c;请允许我叫您一声“大神”&#xff01; 如今&#xff0c;人们与手机的关系就跟电影中我们看到的那些烟鬼与鸦片的关系一样。或许会…

金立软件测试员,6GB运存有多强 金立M2017打开APP测试

原标题&#xff1a;6GB运存有多强 金立M2017打开APP测试 安卓手机越用越卡这已经是一个不争的事实&#xff0c;其中的导致原因有很多&#xff0c;但是解决卡顿的方法却只有两个&#xff1a;一个是系统优化&#xff0c;另一个就是增加运行内存。在系统优化方面由于安卓系统机制原…