前几天,有个同事在使用JPA的自定义SQL方法时,程序一直报异常,捣鼓了半天也没能解决,咨询我的时候,我看了一眼他的程序,差不多是这个样子的:

 @Repository
public interface UserRepository extends JpaRepository<User,Long> { @Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
void deleteUserById(Long id);
}

  我告诉他,你的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联合使用,派生类的查询方法和自定义的方法不需要此注解,如:

 @Repository
public interface UserRepository extends JpaRepository<User,Long> { // 父类的保存方法
@Override
User save(User entity); // 按照JPA语法规则自定义的查询方法
List<User> findFirst10ByLastname(String lastName, Pageable pageable);
}

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

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

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

  2)使用@Modifying来标注是一个更新类的自定义语句。

  按照这个规则,修改同事的那个方法:

  @Repository
public interface UserRepository extends JpaRepository<User,Long> { @Modifying
@Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
void deleteUserById(Long id);
}

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

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

 public interface UserRepository extends CrudRepository<User, Long> {

   @Override
@Transactional(timeout = 10)
public List<User> findAll(); // Further query method declarations
}

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

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

 @Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional { Propagation propagation() default Propagation.REQUIRED; Isolation isolation() default Isolation.DEFAULT; int timeout() default -1; boolean readOnly() default false; // 其他省略
}

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

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

 @Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> { @Transactional
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
} @Transactional
public void delete(T entity) {
Assert.notNull(entity, "The entity must not be null!");
this.em.remove(this.em.contains(entity) ? entity : this.em.merge(entity));
} @Transactional
public void deleteAll(Iterable<? extends T> entities) {
Assert.notNull(entities, "The given Iterable of entities not be null!");
Iterator var2 = entities.iterator(); while(var2.hasNext()) {
T entity = var2.next();
this.delete(entity);
}
} public T getOne(ID id) {
Assert.notNull(id, "The given id must not be null!");
return this.em.getReference(this.getDomainClass(), id);
} public List<T> findAll() {
return this.getQuery((Specification)null, (Sort)Sort.unsorted()).getResultList();
} public List<T> findAll(@Nullable Specification<T> spec) {
return this.getQuery(spec, Sort.unsorted()).getResultList();
} public List<T> findAll(@Nullable Specification<T> spec, Sort sort) {
return this.getQuery(spec, sort).getResultList();
} public <S extends T> long count(Example<S> example) {
return executeCountQuery(this.getCountQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType()));
} public <S extends T> boolean exists(Example<S> example) {
return !this.getQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType(), (Sort)Sort.unsorted()).getResultList().isEmpty();
} @Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
} @Transactional
public <S extends T> S saveAndFlush(S entity) {
S result = this.save(entity);
this.flush();
return result;
} @Transactional
public void flush() {
this.em.flush();
}
}

  从SimpleJpaRepository源码中可以看出:

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

2)该类的所有查询类操作方法都与类相同,都拥有只读事务;

3)该类的所有保存、更新、删除操作方法都用@Transactional重新注解了(默认readOnly=false)。

  说明JPA为我们提供的所有方法,包括JPA规则的自定义方法在其底层都为我们做好了事务处理,而我们自定义的方法需要自己来标注事务的类型是只读还是非只读。根据这个原理,再次修改开篇所列出的方法:

 @Repository
public interface UserRepository extends JpaRepository<User,Long> { @Transactional
@Modifying
@Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
void deleteUserById(Long id);
}

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

三、@Modifying注解补充说明

 @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface Modifying { boolean flushAutomatically() default false; boolean clearAutomatically() default false;
}

  该注解中有两个属性:flushAutomatically、clearAutomatically,从字面理解是自动刷新和自动清除。

  自动刷新,即执行完语句后立即将变化内容刷新到磁盘,如果是insert语句操作,则与JPA的<S extends T> S saveAndFlush(S entity);方法效果相同;

  自动清除,即执行完语句后自动清除掉已经过期的实体,比如,我们删除了一个实体,但是在还没有执行flush操作时,这个实体还存在于实体管理器EntityManager中,但这个实体已经过期没有任何用处,直到flush操作时才会被删除掉。如果希望在删除该实体时立即将该实体从实体管理器中删除,则可以将该属性设置为true,如:

 @Modifying(clearAutomatically = true)
@Transactional
@Query(value = "delete from pro_user where id = ?1",nativeQuery = true)
void deleteUserById(Long id);

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

  1. sqlserver 插入 更新 删除 语句中的 output子句

    官方文档镇楼: https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/ms177564(v=sql.100) 从 ...

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

    我的MYSQL学习心得(八) 插入 更新 删除 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得( ...

  3. MySql中4种批量更新的方法update table2,table1,批量更新用insert into ...on duplicate key update, 慎用replace into.

    mysql 批量更新记录 MySql中4种批量更新的方法最近在完成MySql项目集成的情况下,需要增加批量更新的功能,根据网上的资料整理了一下,很好用,都测试过,可以直接使用. mysql 批量更新共 ...

  4. oracle插入,更新,删除数据

    插入,更新,删除数据 oracle提供了功能丰富的数据库管理语句 包括有效的向数据库中插入数据的insert语句 更新数据的update语句 以及当数据不再使用时删除数据的delete语句 更改数据之 ...

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

    向oracle中的表插入数据的方法有以下几种: 假设表名为User 第一种方法:select t.*,rowid from User t;-->点击钥匙那个标记就可向表中添加数据 第二种方法:s ...

  6. Postgresql中无则插入的使用方法INSERT INTO WHERE NOT EXISTS

    一.问题 Postgresql中无则插入的使用方法INSERT INTO WHERE NOT EXISTS,用法请参考样例. 二.解决方案 (1)PostgresSQL INSERT INTO tes ...

  7. MariaDB 插入&更新&删除数据(8)

    MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,MySQL由于现在闭源了,而能轻松成为MySQ ...

  8. C#中往数据库插入/更新时候关于NUll空值的处理

    本文转载:http://blog.csdn.net/chybaby/article/details/2338943 本文转载:http://www.cnblogs.com/zfanlong1314/a ...

  9. Java 获取Word中的所有插入和删除修订

    在 Word 文档中启用跟踪更改功能后,会记录文档中的所有编辑行为,例如插入.删除.替换和格式更改.对插入或删除的内容,可通过本文中介绍的方法来获取. 引入Jar 方法1 手动引入:将 Free Sp ...

随机推荐

  1. CSS层叠和继承

    CSS具有两个核心的概念--继承和层叠.一般文本类的属性会被继承,即某个元素的CSS属性会传递给内部嵌套的元素.一个元素可能有一个或者多个样式的来源,当属性发生冲突时,就会根据加载顺序和权重大小决定层 ...

  2. Xamarin.Android 使用AsyncTask提示上传动态

    我们有时候会通过WebServices上传数据,如果信息量过大并没有提示,用户会觉得是死机,或是系统崩溃,这时候我们可以用到AsyncTask(异步任务)来提示上传信息,例如:正在上传数据... 这里 ...

  3. spring中注解式事务不生效的问题

    常用的解决方法可以百度,我针对我的问题描述一下 Mysql中InnoDB引擎才支持事务, MyISAM不支持事务. 当你尝试了各种方法解决spring中注解式事务不生效时, 一定要查看一下数据库中表的 ...

  4. Js的String对象

    Js的String对象常用方法: 方法一.得到某字符在字符串中的索引位置. str.indexOf(findStr,[index])--返回的是要查找字符在字符串中的位置索引   ,index开始查找 ...

  5. mybatis延迟加载详解

    http://www.cnblogs.com/selene/p/4631244.html http://blog.csdn.net/eson_15/article/details/51668523

  6. SSM-MyBatis-01:IDEA的安装,永久注册和简单的MyBatis用例

    一,IDEA的安装和永久注册 1.安装: 那到安装包,下一步,选路径,上面可以选操作系统64/32位,下面是程序的默认打开方式,可以不必勾选,也可以全选 路径一定不包含中文,重点 2.永久注册: 将此 ...

  7. 代码方式设置WordPress内所有URL链接都在新标签页打开

    本文由荒原之梦原创,原文链接:http://zhaokaifeng.com/?p=699 前言: WordPress默认情况下几乎所有URL链接都是在同一个标签页打开.这样的话,读者点击一个链接就会离 ...

  8. python函数调用的四种方式 --基础重点

    第一种:参数按顺序从第一个参数往后排#标准调用 # -*- coding: UTF-8 -*- def normal_invoke(x, y): print "--normal_invoke ...

  9. Using variables inside Postman and Collection Runner

    Variables are among the most powerful features in Postman. Using variables in your Postman requests, ...

  10. 译MassTransit 创建消息消费者

    创建消息消费者一个消息消费者是一个 可以消费一个或多个消息类型的类,指定IConsumer<T>接口,T为消息类型 public class UpdateCustomerConsumer ...