这两天需要用springboot jpa做更新处理,但是惊讶的发现 jpa种并没有忽略空值的配置,而且网上搜了一堆解决方案,大致做下整理

1.

在实体类上面添加这两个注解,

@DynamicInsert :设置为true,表示insert对象的时候,生成动态的insert语句,如果这个字段的值是null就不会加入到insert语句当中.默认true。 这个我没测试过,插入的时候,空值是否生成sql那点效率关系不大

@DynamicUpdate:设置为true,在开始的时候,我也以为这是忽略空值,但我测试的时候,发现我把实体类设置为null,去替换数据库里的数据,结果依然update成功了,最后才看见有人说,这是动态更新,只观察值是否有变动,和null无关,不知道网上哪些小伙伴是怎么成功的。

 

2.

在service 或者 Repository层,增加一个切面,每次更新的时候,先通过ID查询一次数据库,然后用beanUtil复制非空字段到目标字段,最后用Repository.save(entity),

弊端:

  1. 线程安全,并发情况下,会出现意外情况,需要在整个方法上加锁,
  2. 每次多一次查询,效率降低

3.

自定义simpleRepository,重写部分save中的代码,取消原来的repository注册,在启动类上重新使用自定义的simpleRepository

优缺点和上面差不多,只是更内层

 

每次必须多做一次查询,让我这个稍微有点强迫证的人很难受,于是自己就去翻源码,google找解决办法,在反复查看源码后,终于找到了一种解决办法,没人实践过,我自己测试没有问题,在这里做个记录:

jpa的更新,属于合并更新,而每次在合并中,都是由一个 DefaultMergeEventListener 的事件监听器来执行copy的,所以,我自定义一个 DefaultMergeEventListener

/**
 * @Auther: by yaoqiang
 * @Date: 2019-10-22 09:59
 * @Description:
 */
public class IgnoreNullEventListener extends DefaultMergeEventListener {

    public static final IgnoreNullEventListener INSTANCE = new IgnoreNullEventListener();

    @Override
    protected void copyValues(EntityPersister persister, Object entity, Object target, SessionImplementor source, Map copyCache) {
        //源目标
        Object[] original = persister.getPropertyValues( entity );
        //存储目标
        Object[] targets = persister.getPropertyValues(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.setPropertyValues( target, copied );
    }
}

在这里面,我将条件做了一些修改,使空值使用了targets中的值,也就是原值,

下一步使将 这个监听器,注入进去,源码翻了半天,找到了注册中心:网上也查了一些资料,注意,这里要将原来的DefaultMergeEventListener清理掉,要不然会循环调用,调用两次listeners,不管自定义的在前面还是在后面都会失败,前面,空值要么使在第二次直接覆盖,后面,空值第一次覆盖,第二次全是空值,这里,我也躺了坑,跟着源码跑,才看出来。

/**
 * @Auther: by yaoqiang
 * @Date: 2019-10-22 09:57
 * @Description:
 */
@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).clear();
        registry.getEventListenerGroup(EventType.MERGE).prependListener(IgnoreNullEventListener.INSTANCE);
    }
}

通过上面的配置,我测试了一个,发现不会在更新空值了,而非空会得到更新,并且调用次数和线程问题也由框架自己解决