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

首先找到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();



    @Override
    protected 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 {


    @PersistenceUnit
    private EntityManagerFactory emf;

    @PostConstruct
    protected 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);
        });

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

你可能感兴趣的:(spring,boot,java,spring)