From e5579c075c5070802de5e93cfcbdf94015d0ff87 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Fri, 13 Oct 2023 16:30:41 +0800 Subject: [PATCH] Enforce modifying methods are annotated with @Transactional Fix GH-3188 --- .../support/SimpleJpaRepository.java | 1 + .../support/SimpleJpaRepositoryUnitTests.java | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 98d839b6458..0c4139a0779 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -468,6 +468,7 @@ public boolean exists(Specification spec) { } @Override + @Transactional public long delete(Specification spec) { CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java index 65ecc0c62d8..bb551a9440e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -16,7 +16,9 @@ package org.springframework.data.jpa.repository.support; import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -31,8 +33,11 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Optional; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,6 +50,7 @@ import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; import org.springframework.data.repository.CrudRepository; +import org.springframework.transaction.annotation.Transactional; /** * Unit tests for {@link SimpleJpaRepository}. @@ -54,6 +60,7 @@ * @author Mark Paluch * @author Jens Schauder * @author Greg Turnquist + * @author Yanming Zhou */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) @@ -216,4 +223,20 @@ void applyQueryHintsToCountQueriesForSpecificationPageables() { verify(metadata).getQueryHintsForCount(); } + + @Test // GH-3188 + void checkTransactionalAnnotation() { + Stream.of(SimpleJpaRepository.class.getDeclaredMethods()).filter(method -> Modifier.isPublic(method.getModifiers()) && + (method.getName().startsWith("delete") || method.getName().startsWith("save"))).forEach( + method -> { + if (!method.isAnnotationPresent(Transactional.class)) { + fail("Method [" + method + "] should be annotated with @Transactional"); + } + Transactional transactional = method.getAnnotation(Transactional.class); + if (transactional.readOnly()) { + fail("Method [" + method + "] should not be annotated with @Transactional(readOnly = true)"); + } + } + ); + } }