網站首頁 編程語言 正文
JPA中自定義的插入、更新、刪除方法為什么要添加@Modifying注解和@Transactional注解?
前幾天,有個同事在使用JPA的自定義SQL方法時,程序一直報異常,搗鼓了半天也沒能解決,咨詢我的時候,我看了一眼他的程序,差不多是這個樣子的:
1 @Repository
2 public interface UserRepository extends JpaRepository<User,Long> {
3
4 @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
5 void deleteUserById(Long id);
6 }
123456
我告訴他,你的deleteUserById方法缺少了@Modifying注解和@Transactional注解,他半信半疑地試了一下,然后果然就解決了。其實,如果他查一下官方資料或許很快也就能找到答案?;谶@個背景,本文詳細講解一下為何我們自定義的插入、更新、刪除操作需要加@Modifying注解和@Transactional注解。
一、@Modifying注解
在官方資料中,給出了這樣幾句說明:
復制代碼
As the queries themselves are tied to the Java method that executes them, you can actually bind them directly by using the Spring Data JPA @Query annotation
rather than annotating them to the domain class.
You can modify queries that only need parameter binding by annotating the query method with @Modifying
The @Modifying annotation is only relevant in combination with the @Query annotation. Derived query methods or custom methods do not require this Annotation.
Doing so triggers the query annotated to the method as an updating query instead of a selecting one.
復制代碼
如下:
@Modifying
@Query(“update User u set u.firstname = ?1 where u.lastname = ?2”)
int setFixedFirstnameFor(String firstname, String lastname);
第一句話的意思是可以用@Query注解來將自定義sql語句綁定到自定義方法上。
第二句話的意思時,可以用@Modifying注解來標注只需要綁定參數的自定義的更新類語句(更新、插入、刪除)。
第三名話的意思是說@Modifying只與@Query聯合使用,派生類的查詢方法和自定義的方法不需要此注解,如:
復制代碼
1 @Repository
2 public interface UserRepository extends JpaRepository<User,Long> {
3
4 // 父類的保存方法
5 @Override
6 User save(User entity);
7
8 // 按照JPA語法規則自定義的查詢方法
9 List<User> findFirst10ByLastname(String lastName, Pageable pageable);
10 }
12345678910
復制代碼
第四句話的意思是,當加上@Modifying注解時,JPA會以更新類語句來執行,而不再是以查詢語句執行。
也就是說,當我們要通過自已寫的更新、插入、刪除SQL語句來實現更新、插入、刪除操作時,至少需要用兩個步驟:
@Query來注入我們自定義的sql;
使用@Modifying來標注是一個更新類的自定義語句。
按照這個規則,修改同事的那個方法:
復制代碼
1 @Repository
2 public interface UserRepository extends JpaRepository<User,Long> {
3
4 @Modifying
5 @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
6 void deleteUserById(Long id);
7 }
1234567
復制代碼
但是,此時,該方法還不完整,執行時程序會報以下錯誤:
復制代碼
org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException:
Executing an update/delete query
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:402)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
…
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query
at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:398)
at org.hibernate.query.internal.AbstractProducedQuery.executeUpdate(AbstractProducedQuery.java:1585)
…
復制代碼
二、@Transactional注解
官方的說明:
By default, CRUD methods on repository instances are transactional. For read operations, the transaction configuration readOnly flag is set to true. All others are configured with a plain @Transactional so that default transaction configuration applies. For details, see JavaDoc of SimpleJpaRepository. If you need to tweak transaction configuration for one of the methods declared in a repository, redeclare the method in your repository interface, as follows:
Example. Custom transaction configuration for CRUD
復制代碼
1 public interface UserRepository extends CrudRepository<User, Long> {
2
3 @Override
4 @Transactional(timeout = 10)
5 public List<User> findAll();
6
7 // Further query method declarations
8 }
12345678
復制代碼
這句話的意思是,默認情況下,repository 接口中的CRUD方法都是被@Transactional注解修飾了的,對于讀的操作方法,@Transactional注解的readOnly屬性是被設置為true的,即只讀;CRUD中的其他方法被@Transactional修飾,即非只讀。如果你需要修改repository 接口中的某些方法的事務屬性,可以在該方法上重新加上@Transactional注解,并設置需要的屬性。
我們先來看一下,@Transactional注解的源碼:
復制代碼
1 @Target({ElementType.METHOD, ElementType.TYPE})
2 @Retention(RetentionPolicy.RUNTIME)
3 @Inherited
4 @Documented
5 public @interface Transactional {
6
7 Propagation propagation() default Propagation.REQUIRED;
8
9 Isolation isolation() default Isolation.DEFAULT;
10
11 int timeout() default -1;
12
13 boolean readOnly() default false;
14
15 // 其他省略
16 }
12345678910111213141516
復制代碼
由上可見@Transactional注解的readOnly默認的屬性的false,即非只讀,當一個事務是非只讀事務的時候,我們可以進行任何操作。
再看一下repository 接口的實現類SimpleJpaRepository的源碼(只摘了部分源碼):
復制代碼
1 @Repository
2 @Transactional(
3 readOnly = true
4 )
5 public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
6
7 @Transactional
8 public void deleteById(ID id) {
9 Assert.notNull(id, "The given id must not be null!");
10 this.delete(this.findById(id).orElseThrow(() -> {
11 return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
12 }));
13 }
14
15 @Transactional
16 public void delete(T entity) {
17 Assert.notNull(entity, "The entity must not be null!");
18 this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
19 }
20
21 @Transactional
22 public void deleteAll(Iterable<? extends T> entities) {
23 Assert.notNull(entities, "The given Iterable of entities not be null!");
24 Iterator var2 = entities.iterator();
25
26 while(var2.hasNext()) {
27 T entity = var2.next();
28 this.delete(entity);
29 }
30 }
31
32 public T getOne(ID id) {
33 Assert.notNull(id, "The given id must not be null!");
34 return this.em.getReference(this.getDomainClass(), id);
35 }
36
37 public List<T> findAll() {
38 return this.getQuery((Specification)null, (Sort)Sort.unsorted()).getResultList();
39 }
40
41 public List<T> findAll(@Nullable Specification<T> spec) {
42 return this.getQuery(spec, Sort.unsorted()).getResultList();
43 }
44
45 public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
46 return this.getQuery(spec, sort).getResultList();
47 }
48
49 public <S extends T> long count(Example<S> example) {
50 return executeCountQuery(this.getCountQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType()));
51 }
52
53 public <S extends T> boolean exists(Example<S> example) {
54 return !this.getQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType(), (Sort)Sort.unsorted()).getResultList().isEmpty();
55 }
56
57 @Transactional
58 public <S extends T> S save(S entity) {
59 if (this.entityInformation.isNew(entity)) {
60 this.em.persist(entity);
61 return entity;
62 } else {
63 return this.em.merge(entity);
64 }
65 }
66
67 @Transactional
68 public <S extends T> S saveAndFlush(S entity) {
69 S result = this.save(entity);
70 this.flush();
71 return result;
72 }
73
74 @Transactional
75 public void flush() {
76 this.em.flush();
77 }
78 }
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
復制代碼
從SimpleJpaRepository源碼中可以看出:
1)該類上注解了只讀事務@Transactional(readOnly = true);
2)該類的所有查詢類操作方法都與類相同,都擁有只讀事務;
3)該類的所有保存、更新、刪除操作方法都用@Transactional重新注解了(默認readOnly=false)。
123
說明JPA為我們提供的所有方法,包括JPA規則的自定義方法在其底層都為我們做好了事務處理,而我們自定義的方法需要自己來標注事務的類型是只讀還是非只讀。根據這個原理,再次修改開篇所列出的方法:
復制代碼
1 @Repository
2 public interface UserRepository extends JpaRepository<User,Long> {
3
4 @Transactional
5 @Modifying
6 @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
7 void deleteUserById(Long id);
8 }
12345678
復制代碼
至此,該方法按所期望的結果運行成功了。
三、@Modifying注解補充說明
復制代碼
1 @Retention(RetentionPolicy.RUNTIME)
2 @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
3 @Documented
4 public @interface Modifying {
5
6 boolean flushAutomatically() default false;
7
8 boolean clearAutomatically() default false;
9 }
12345678910
復制代碼
該注解中有兩個屬性:flushAutomatically、clearAutomatically,從字面理解是自動刷新和自動清除。
自動刷新,即執行完語句后立即將變化內容刷新到磁盤,如果是insert語句操作,則與JPA的 ~S saveAndFlush(S entity);方法效果相同;~
自動清除,即執行完語句后自動清除掉已經過期的實體,比如,我們刪除了一個實體,但是在還沒有執行flush操作時,這個實體還存在于實體管理器EntityManager中,但這個實體已經過期沒有任何用處,直到flush操作時才會被刪除掉。如果希望在刪除該實體時立即將該實體從實體管理器中刪除,則可以將該屬性設置為true,如:
1 @Modifying(clearAutomatically = true)
2 @Transactional
3 @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
4 void deleteUserById(Long id);
1234
原文鏈接:https://blog.csdn.net/qq_43985303/article/details/135377501
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-06-18 使用OpenGL創建窗口的示例詳解_C 語言
- 2022-04-17 aspx頁面報“XPathResult未定義”的解決方法
- 2023-05-15 Go語言實現AES加密并編寫一個命令行應用程序_Golang
- 2022-06-20 深入淺析C#?11?對?ref?和?struct?的改進_C#教程
- 2022-06-08 Spring Cloud Nacos NacosWatch
- 2022-06-24 Python抽象類應用詳情_python
- 2022-04-03 Android?App應用退到后臺顯示通知的實現方法_Android
- 2022-11-12 CSS單標簽實現復雜的棋盤布局_經驗交流
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支