• 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);
  • 相关阅读:
    家庭记账本安卓版开发:第一天
    家庭记账本安卓版开发:第二天
    通过Android的API对Sqlite数据库进行操作
    通过SQL语句操作Sqlite数据库
    Activity组件(四):通过requestCode和resultCode来实现Activity间的数据回传
    Activity组件(三):通过对象实现信息添加及展示
    家庭记账本安卓版开发:第三天
    梦断代码(三)
    MacType使用配置
    去掉win7快捷方式箭头及修复锁定到任务栏失效
  • 原文地址:https://www.cnblogs.com/wuhenzhidu/p/jpa.html
Copyright © 2020-2023  润新知