springboot jpa 關於save保存空值的問題整理

這兩天需要用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();

        System.out.println("test");

        int len = 0;

        for(int i = 0;i<original.length;i++){
            if(original[i] != null){
                len ++;
            }
        }

        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);
    }
}

通過上面的配置,我測試了一個,發現不會在更新空值了,而非空會得到更新,並且調用次數和線程問題也由框架自己解決

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章