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

  第四句话的意思是,当加上@Modifying注解时,JPA会以更新类语句来执行,而不再是以查询语句执行。  

  也就是说,当我们要通过自已写的更新、插入、删除SQL语句来实现更新、插入、删除操作时,至少需要用两个步骤:

  1)@Query来注入我们自定义的sql;

  2)使用@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 @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 }

  由上可见@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 }

  从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 extends T> 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);

原文地址:https://www.cnblogs.com/wuhenzhidu/p/jpa.html

时间: 2024-10-30 16:26:36

JPA中自定义的插入、更新、删除方法为什么要添加@Modifying注解和@Transactional注解?的相关文章

DButils工具类可以用来获取数据库连接向数据库插入更新删除对象2

package com.ctl.util; import java.awt.Color; import java.awt.Font; import java.awt.Insets; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.*; import java.lang.reflect.*; import java.sql.*; import java.text.SimpleD

向oracle中的表插入数据的方法

向oracle中的表插入数据的方法有以下几种: 假设表名为User 第一种方法:select t.*,rowid from User t;-->点击钥匙那个标记就可向表中添加数据 第二种方法:select t.*,rowid from User t for update;-->点击钥匙那个标记就可向表中添加数据 第三种方法:在oracle中的table表中选中你要添加数据的那个表-->点击右键-->选中Edit data-->就可向表中添加数据 第四种方法:通过DAO层来添加

Spring Data JPA 中使用Update Query更新实体类问题

在jpa中使用@Modifying 虽然事务已经能够更新,但是在循环更新的时候,执行modify语句后的查询的实体仍然是没有更新的. 执行完modifying query, EntityManager可能会包含过时的数据,因为EntityManager不会自动清除实体.只有添加clearAutomatically属性,EntityManager才会自动清除实体对象. @Modifying(clearAutomatically = true)

jpa中的一对多级联删除

jpa中如果要级联删除一方对应的多方的记录,必须在多对建立与一方之间的多对一关系,否则级联删除不了,如下: public class ChannelEntity implements java.io.Serializable { ....         private List<ChannelDayLimitEntity> chanelDayLimits; @OneToMany(mappedBy="channel", cascade=CascadeType.REMOVE,

MySQL优化之——插入 更新 删除

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46581769 插入 将多行查询结果插入到表中 语法 INSERT INTO table_name1(column_list1) SELECT (column_list2) FROM table_name2 WHERE (condition) table_name1指定待插入数据的表:column_list1指定待插入表中要插入数据的哪些列:table_name2指定插入数据是从

我的MYSQL学习心得(八) 插入 更新 删除

这一篇<我的MYSQL学习心得(八)>将会讲解MYSQL的插入.更新和删除语句 同样的,只会讲解跟SQLSERVER不同的地方 插入 将多行查询结果插入到表中 语法 INSERT INTO table_name1(column_list1) SELECT (column_list2) FROM table_name2 WHERE (condition) INSERT INTO SELECT 在SQLSERVER里也是支持的 table_name1指定待插入数据的表:column_list1指定

插入,更新,删除数据

数据插入 INSERT是用来插入或者添加行到数据库表的,插入可以分为几种方法 插入完整的行 插入行的一部分 插入多行 插入某些查询的结果 插入完整的行 mysql> INSERT INTO customers VALUES(NULL, 'zhangsan', 'shandong', NULL, NULL, NULL, NULL, '15053631234', '[email protected]'); Query OK, 1 row affected (0.01 sec) 此例子插入一个新客户到

使用angular中自定义的directive实现删除确认框

我的删除情况,可单独删除一条任务,也可以根据类别删除(类别和所属此类别的任务集合). 首先页面 task.ng.html <span ng-click="remove(task)" confirm><span class="glyphicon glyphicon-trash icon" ></span></span> TaskController $scope.ok = function (task) { $scope

PHP中调用SVN命令更新网站方法(解决文件名包含中文更新失败的问题)

想说写一个通过网页就可以执行 SVN 升级的程序,结果并不是我想得那样简单,有一些眉角需要注意的说. 先以 Apache 的用户帐号执行 SVN checkout,这样 Apache 才有 SVN 的链结权力,才可以通过网页执行 SVN update su -s /bin/bash www-data cd /var/www svn checkout http://www.xxx.com/svn/my_site 在用 PHP 执行 shell 指令前要加上 export LANG=C.UTF-8