辽宁东方建设工程有限公司网站,莱芜百度网站建设,陕西省住房建设厅官网,做网站的公司那家好。JPA中自定义的插入、更新、删除方法为什么要添加Modifying注解和Transactional注解#xff1f; 前几天#xff0c;有个同事在使用JPA的自定义SQL方法时#xff0c;程序一直报异常#xff0c;捣鼓了半天也没能解决#xff0c;咨询我的时候#xff0c;我看了一眼他的程…JPA中自定义的插入、更新、删除方法为什么要添加Modifying注解和Transactional注解 前几天有个同事在使用JPA的自定义SQL方法时程序一直报异常捣鼓了半天也没能解决咨询我的时候我看了一眼他的程序差不多是这个样子的
1 Repository
2 public interface UserRepository extends JpaRepositoryUser,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 JpaRepositoryUser,Long {3 4 // 父类的保存方法5 Override6 User save(User entity); 7 8 // 按照JPA语法规则自定义的查询方法9 ListUser findFirst10ByLastname(String lastName, Pageable pageable);
10 }复制代码 第四句话的意思是当加上Modifying注解时JPA会以更新类语句来执行而不再是以查询语句执行。
也就是说当我们要通过自已写的更新、插入、删除SQL语句来实现更新、插入、删除操作时至少需要用两个步骤
Query来注入我们自定义的sql
使用Modifying来标注是一个更新类的自定义语句。
按照这个规则修改同事的那个方法
复制代码
1 Repository
2 public interface UserRepository extends JpaRepositoryUser,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 CrudRepositoryUser, Long {
2
3 Override
4 Transactional(timeout 10)
5 public ListUser 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 SimpleJpaRepositoryT, ID implements JpaRepositoryImplementationT, 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 ListT findAll() {
38 return this.getQuery((Specification)null, (Sort)Sort.unsorted()).getResultList();
39 }
40
41 public ListT findAll(Nullable SpecificationT spec) {
42 return this.getQuery(spec, Sort.unsorted()).getResultList();
43 }
44
45 public ListT findAll(Nullable SpecificationT spec, Sort sort) {
46 return this.getQuery(spec, sort).getResultList();
47 }
48
49 public S extends T long count(ExampleS example) {
50 return executeCountQuery(this.getCountQuery(new SimpleJpaRepository.ExampleSpecification(example), example.getProbeType()));
51 }
52
53 public S extends T boolean exists(ExampleS 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重新注解了默认readOnlyfalse。说明JPA为我们提供的所有方法包括JPA规则的自定义方法在其底层都为我们做好了事务处理而我们自定义的方法需要自己来标注事务的类型是只读还是非只读。根据这个原理再次修改开篇所列出的方法
复制代码
1 Repository
2 public interface UserRepository extends JpaRepositoryUser,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);