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 }

我告诉他,你的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 @Repository2 public interface UserRepository extends JpaRepository<User,Long> {3 4     // 父类的保存方法5     @Override6     User save(User entity); 7 8     // 按照JPA语法规则自定义的查询方法9     List<User> findFirst10ByLastname(String lastName, Pageable pageable);
10 }

复制代码
  第四句话的意思是,当加上@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  }

复制代码
  但是,此时,该方法还不完整,执行时程序会报以下错误:

复制代码
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 }

复制代码
  这句话的意思是,默认情况下,repository 接口中的CRUD方法都是被@Transactional注解修饰了的,对于读的操作方法,@Transactional注解的readOnly属性是被设置为true的,即只读;CRUD中的其他方法被@Transactional修饰,即非只读。如果你需要修改repository 接口中的某些方法的事务属性,可以在该方法上重新加上@Transactional注解,并设置需要的属性。

我们先来看一下,@Transactional注解的源码:

复制代码

 1 @Target({ElementType.METHOD, ElementType.TYPE})2 @Retention(RetentionPolicy.RUNTIME)3 @Inherited4 @Documented5 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 }

复制代码
  由上可见@Transactional注解的readOnly默认的属性的false,即非只读,当一个事务是非只读事务的时候,我们可以进行任何操作。

再看一下repository 接口的实现类SimpleJpaRepository的源码(只摘了部分源码):

复制代码

 1 @Repository2 @Transactional(3     readOnly = true4 )5 public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {6    7     @Transactional8     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 }

复制代码
  从SimpleJpaRepository源码中可以看出:

1)该类上注解了只读事务@Transactional(readOnly = true);

   2)该类的所有查询类操作方法都与类相同,都拥有只读事务;3)该类的所有保存、更新、删除操作方法都用@Transactional重新注解了(默认readOnly=false)。

说明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 }

复制代码
  至此,该方法按所期望的结果运行成功了。

三、@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 }

复制代码
  该注解中有两个属性: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);

@Modifying注解和@Transactional注解?相关推荐

  1. 解释@Transactional注解的用法

    @Transactional可以说是spring中最常用的注解之一了,通常情况下我们在需要对一个service方法添加事务时,加上这个注解,如果发生unchecked exception,就会发生ro ...

  2. @Transactional注解和@Modifying注解

    这两个注解相信很多初学者还没有弄懂,在这里进行一个简单的解释. @Modifying注解需要在涉及到数据修改操作时在@query上引用,通俗易懂的来说就是平时查询操作的话不需要加,涉及到数据修改时需要 ...

  3. @Transactional注解最容易忽视的三个失效场景!

    @Transactional注解在以下场景中使用,是会失效的,切记! 1.非public方法 spring对注解事务的方法进行校验,修饰符是不是public,不是 public则不会获取@Transa ...

  4. @aspect注解类不生效_springboot:@Transactional注解 VS @Service注解

    1. Transactional注解与Service/Component注解冲突? 之前遇到一个神奇的事情--用Transactional注解的方法,数据处理了一半,后面的数据处理抛出异常后,没有回滚 ...

  5. 一口气说出 6 种 @Transactional 注解的失效场景

    一.事务 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种. 编程式事务:是指在代码中手动的管理事务的提交.回滚等操作,代码侵入性比较强, ...

  6. spring@Transactional注解事务不回滚不起作用无效的问题处理

    这几天在项目里面发现我使用@Transactional注解事务之后,抛了异常居然不回滚.后来终于找到了原因. 如果你也出现了这种情况,可以从下面开始排查. 一.特性 先来了解一下@Transactio ...

  7. @Transactional注解在什么情况下失效?

    引言 1.@Transactional 注解相信大家并不陌生,平时开发中很常用的一个注解,它能保证方法内多个数据库操作要么同时成功.要么同时失败. 2.使用@Transactional注解时需要注意许 ...

  8. 为什么加了@Transactional注解,事务没有回滚?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 在前天的<事务管理入门>一文发布之后,有读者 ...

  9. java事务不生效场景_springboot中使用@Transactional注解事物不生效的坑

    一:在springboot中使用事物遇到的坑 1.我们知道spring中的事物分为两种:一种是编程式事物,一种是声明式事物.顾名思义,编程式事物是指通过代码去实现事物管理,这里不做过多说明.另一种是声 ...

最新文章

  1. vue与html优势与缺点,vue优缺点详解
  2. 危害企业IT系统最严重的五个安全威胁
  3. 【翻译】安卓新播放器EXOplayer介绍
  4. 修改shape数据 小数位数_【数据管理】Excel实用精华
  5. clover引导mbr安装黑苹果_安装黑苹果记录(一)
  6. 【问题收集·知识储备】Xcode只能选择My Mac,不能选择模拟器如何解决?
  7. 面向对象语言的技术特点
  8. ADS中startup.s文件启动分析
  9. php中写alter,MySQL之alter语句用法总结
  10. 复杂多个合约部署验证方法
  11. 带有SeekBar的Android Media Player歌曲
  12. asp.net中如何退出整个框架(frameset ),回到登录界面?
  13. 基于YACC的TINY语法分析器的构建
  14. 越狱开发笔记(三)——非越狱App砸壳
  15. Python+OpenCV实现sobel边缘检测
  16. OSChina 周四乱弹 —— 八字欠备,五行缺胎
  17. 解决Windows 10控制面板里原本的索引选项变成Indexing Option Control Panel (32-bit)及空白图标的问题
  18. Java ClassCastException: xxx cannot be cast to xxx 问题4种情况解决(Java、EasyPoi、JFreeChart、EasyExcel)
  19. Arrays.copyOf 编译器提示 Usage of API documented as @since 1.6+ less... (Ctrl+F1) This inspection finds
  20. 如何解决移动硬盘弹出时报错:设备正在使用中

热门文章

  1. 清华OJ:PA 1-2祖玛(Zuma)顺序表方案
  2. Java SSM房屋租赁管理系统(附源码)
  3. javascpirt + HTML实现开关点亮灯泡
  4. Servlet-客户端请求
  5. 加载演员维度表和电影维度表
  6. POJ1664 放苹果问题(递归)
  7. 做好小红书推广的三大要点解析_云媒易
  8. CVPR 2021 论文大盘点-图像修复篇
  9. nginx 二次修改https 反向代理服务器失败
  10. ettercap 局域网嗅探