JPA中自定义的插入、更新、删除方法为什么要添加@Modifying注解和@Transactional注解?
前几天,有个同事在使用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注解?的更多相关文章
- sqlserver 插入 更新 删除 语句中的 output子句
官方文档镇楼: https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/ms177564(v=sql.100) 从 ...
- 我的MYSQL学习心得(八) 插入 更新 删除
我的MYSQL学习心得(八) 插入 更新 删除 我的MYSQL学习心得(一) 简单语法 我的MYSQL学习心得(二) 数据类型宽度 我的MYSQL学习心得(三) 查看字段长度 我的MYSQL学习心得( ...
- MySql中4种批量更新的方法update table2,table1,批量更新用insert into ...on duplicate key update, 慎用replace into.
mysql 批量更新记录 MySql中4种批量更新的方法最近在完成MySql项目集成的情况下,需要增加批量更新的功能,根据网上的资料整理了一下,很好用,都测试过,可以直接使用. mysql 批量更新共 ...
- oracle插入,更新,删除数据
插入,更新,删除数据 oracle提供了功能丰富的数据库管理语句 包括有效的向数据库中插入数据的insert语句 更新数据的update语句 以及当数据不再使用时删除数据的delete语句 更改数据之 ...
- 向oracle中的表插入数据的方法
向oracle中的表插入数据的方法有以下几种: 假设表名为User 第一种方法:select t.*,rowid from User t;-->点击钥匙那个标记就可向表中添加数据 第二种方法:s ...
- Postgresql中无则插入的使用方法INSERT INTO WHERE NOT EXISTS
一.问题 Postgresql中无则插入的使用方法INSERT INTO WHERE NOT EXISTS,用法请参考样例. 二.解决方案 (1)PostgresSQL INSERT INTO tes ...
- MariaDB 插入&更新&删除数据(8)
MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,MySQL由于现在闭源了,而能轻松成为MySQ ...
- C#中往数据库插入/更新时候关于NUll空值的处理
本文转载:http://blog.csdn.net/chybaby/article/details/2338943 本文转载:http://www.cnblogs.com/zfanlong1314/a ...
- Java 获取Word中的所有插入和删除修订
在 Word 文档中启用跟踪更改功能后,会记录文档中的所有编辑行为,例如插入.删除.替换和格式更改.对插入或删除的内容,可通过本文中介绍的方法来获取. 引入Jar 方法1 手动引入:将 Free Sp ...
随机推荐
- Elasticsearch JavaApi
官网JavaApi地址:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html 博 ...
- JS window对象的top、parent、opener含义介绍(转载)
1.top该变更永远指分割窗口最高层次的浏览器窗口.如果计划从分割窗口的最高层次开始执行命令,就可以用top变量. 2.openeropener用于在window.open的页面引用执行该window ...
- JavaScript高级程序设计(一)
一.三种常见的著名的命名规则: 1.Camel(驼峰式命名):首字母是小写的,接下来的单词都以大写字母开头.例如:var myTestValue=0; 2.Pascal(帕斯卡命名):首字母是大写的 ...
- thinkphp实现数据分页
方法一: public function show_cate(){ $category_name = array( '1' => '政法综治前沿', '2' => '政策法规', '3' ...
- 条件随机场CRF(二) 前向后向算法评估标记序列概率
条件随机场CRF(一)从随机场到线性链条件随机场 条件随机场CRF(二) 前向后向算法评估标记序列概率 条件随机场CRF(三) 模型学习与维特比算法解码 在条件随机场CRF(一)中我们总结了CRF的模 ...
- 面试真题--------spring源码解析IOC
spring是我经常使用的框架,可是你真的对spring理解吗? 还是只知道它得使用.如果你想知道它真实的面目请仔细向下看. 1.spring是如何知道哪些Bean需要实例化的? 容器启动过程中,首先 ...
- linux学习(JDK,Tomcat,nginx)安装
最近学习了在linux,在上面搭建了一个tomcat的服务器,后来又使用nginx进行反向代理了一下下,希望文章对初学者有所帮助. 1.安装JDK.(采用jdk的rpm包进行安装) 安装jdk之前需要 ...
- SSM-Spring-02:Spring的DI初步加俩个实例
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- DI:依赖注入 第一个DEMO:域属性注入 java类:(Car类和Stu类,学生有一辆小汽车) packag ...
- Markdown编辑器editor.md的使用---markdown上传图片
http://kindeditor.org/ 确定下有没有查找替换功能 http://pandao.github.io/editor.md/ http://pandao.github.io/edito ...
- SSH概念及常用操作汇总
工作有一段时间了,经常用SSH登录远程机器,但对原理一直不是很了解,所以查阅了一些资料,写个小结. 一. SSH是什么? SSH的全称是Secure Shell, 是一种“用来在不安全的网络上安全地运 ...