這兩天需要用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),
弊端:
- 線程安全,併發情況下,會出現意外情況,需要在整個方法上加鎖,
- 每次多一次查詢,效率降低
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);
}
}
通過上面的配置,我測試了一個,發現不會在更新空值了,而非空會得到更新,並且調用次數和線程問題也由框架自己解決