基於Spring Boot使用JpaRepository刪除數據時的註意事項
問題:
在Spring Boot中使用JpaRepository的deleteById(ID id)方法刪除數據時,首先要使用existsById(ID id)方法判斷數據是否存在。如果存在,再刪除。
否則,刪除一個id不存在的數據會拋出org.springframework.dao.EmptyResultDataAccessException異常:
2019-01-02 15:57:24.122 WARN org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration$JpaWebConfiguration$JpaWebMvcConfiguration Line:234 - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning 2019-01-02 15:57:24.673 ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] Line:175 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.EmptyResultDataAccessException: No class com.qiqi.model.entity.UserBean entity with id 33 exists!] with root cause org.springframework.dao.EmptyResultDataAccessException: No class com.qiqi.model.entity.UserBean entity with id 33 exists! at org.springframework.data.jpa.repository.support.SimpleJpaRepository.lambda$deleteById$0(SimpleJpaRepository.java:150) at org.springframework.data.jpa.repository.support.SimpleJpaRepository$$Lambda$798/1206249587.get(Unknown Source) at java.util.Optional.orElseThrow(Optional.java:290) at org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteById(SimpleJpaRepository.java:149) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:359) at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:200) at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:644) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:608) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor$$Lambda$787/1363172555.get(Unknown Source) at org.springframework.data.repository.util.QueryExecutionConverters$$Lambda$786/1029051888.apply(Unknown Source) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
在使用其他方法時,例如:deleteAllByName(name),不進行判斷也可以刪除,不會拋出異常。
springboot的jpa數據庫操作的坑
前一段用springboot寫瞭篇 springboot整合多數據源小博文,從三個數據庫裡面抓取合適的數據。存在另外一個數據庫裡面。在客戶生產環境運行瞭一段時間,感覺似乎很良好。
客戶覺得意猶未盡,又提瞭點需求,順便提瞭點bug,於是乎又改瞭改代碼。客戶居然提出一個問題,說有時候查不出數據來,過一會又好瞭,我在本地試瞭試,發現在本地竟然也存在這個問題。問題其實一直都有,隻是似乎不影響什麼,所以便沒當一回事。
經過反復測試,原來是往數據庫寫數據的時候卡住瞭,有點奇怪。大概過程是先把表裡面數據清除,然後再寫入,數據不到1000條,居然耗時差不多10秒,什麼springboot,什麼jpa太不靠譜瞭吧?
看代碼 ,Repository
package net.springboot.repository.sqlserver; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import net.springboot.entity.sqlserver.RealData; public interface XXDataRepository extends JpaRepository<XXData, String> { }
調用代碼也是簡單明瞭
db.deleteAll(); db.saveAll(list); //組合list這裡就不寫瞭
其實說白瞭,沒有自己的代碼,都是springboot + jpa 框架實現的,框架難道有問題,這個一般不會吧。把SQL放出來看看。
原來這個樣子,刪除全表數據,居然是一條一條數據刪除,批量保存居然是先查詢一下,然後再插入,JPA難道不考慮效率的嗎?
問題找到瞭,怎麼解決瞭?刪除功能好辦,自己寫SQL嘛,簡單方便,翠花上川菜,代碼拿來。
@Transactional @Modifying @Query(value = "TRUNCATE TABLE table",nativeQuery = true) int TruncateTable(); @Transactional @Modifying @Query(value = "delete from table",nativeQuery = true) int deleteTable();
效果是立竿見影,刪除效率上來瞭。清空表裡數據一秒不到。不過,後來又仔細看瞭一下,jpa似乎還提供瞭另外一個刪除全部數據的方法 deleteAllInBatch,這個方法在刪除前似乎沒有查詢,懶得做測試瞭,習慣瞭自己寫SQL解決問題。
但是批量插入這個不好辦瞭,總不可能自己寫成一條一條插入啊,那還不如不改瞭。百度一下,網上說改一下配置文件即可。
spring.jpa.properties.hibernate.jdbc.batch_size=500 spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true spring.jpa.properties.hibernate.order_inserts=true spring.jpa.properties.hibernate.order_updates=true
但是好像效果不行,show sql 還是一樣,先查詢後插入,效率依然不行。想瞭很多,百度瞭很多,為什麼瞭,為什麼啊?JPA這玩意為什麼會在插入前查詢一下瞭,查詢又是怎麼個查詢方式瞭?這個應該與主鍵ID有關系。所以改一下實體類,id統一為uuid模式。
@Id @GenericGenerator(name = "id-generator", strategy = "uuid") @GeneratedValue(generator = "id-generator") @Column(name = "pid") public String pid;
效果明顯,問題立馬解決。但是有的系統主鍵ID是生成好的,有自己的規則,不可以隨便uuid,比如我這個系統就是,都是在各個系統裡面已經生成好瞭,而且還因為業務需要不能改。
沒辦法隻有另加一個字段做為@id 雖然沒啥實際意義,但是批量寫入數據的問題得到徹底解決,你好,我好,大傢好。
不過話說回來,插入前查詢一下,這個功能是可以有,在大多數的業務場景也是很有用的。springboot的jpa就這樣,在系統中,具體怎麼用,碼農們各顯神通。
也算是趟過 springboot,jpa框架的兩個坑。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 解決springboot的findOne方法沒有合適的參數使用問題
- spring boot之使用spring data jpa的自定義sql方式
- 優化spring boot應用後6s內啟動內存減半
- 解決springboot啟動失敗的問題('hibernate.dialect' not set)
- SpringBoot項目中建議關閉Open-EntityManager-in-view原因