41

1 quick question on Spring JPA repositories transactionality. I have a service that is not marked as transactional and calls Spring JPA repository method

userRegistrationRepository.deleteByEmail(email);

And it is defined as

@Repository
public interface UserRegistrationRepository extends JpaRepository<UserRegistration, Long> {

    UserRegistration findByEmail(String email);

    void deleteByEmail(String email);

}

The problem is that it fails with "No EntityManager with actual transaction available for current thread - cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException" exception.

Ok, I can solve it by marking the service or deleteByEmail(..) method as transactional, but I just can't understand why it crashes now. Spring documentation explicitly states that "CRUD methods on repository instances are transactional by default." (http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions), but apparently this one is not... So Is this statement related to only members of CrudRepository?

ps: that's for Spring Data JPA 1.9.4

FlasH from Ru
  • 1,165
  • 2
  • 13
  • 19

1 Answers1

68

You are right. Only CRUD methods (CrudRepository methods) are by default marked as transactional. If you are using custom query methods you should explicitly mark it with @Transactional annotation.

@Repository
public interface UserRegistrationRepository extends JpaRepository<UserRegistration, Long> {

    UserRegistration findByEmail(String email);

    @Transactional
    void deleteByEmail(String email);

}

You should also be aware about consequences of marking repository interface methods instead of service methods. If you are using default transaction propagation configuration (Propagation.REQUIRED) then:

The transaction configuration at the repositories will be neglected then as the outer transaction configuration determines the actual one used.

http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions

If you want more information about how it is implemented, take a look at default CrudRepository / JpaRepository implementation - SimpleJpaRepository (which you are probably using):

https://github.com/spring-projects/spring-data-jpa/blob/main/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java

The interesting lines are here:

@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

and some of transactional methods here:

@Transactional
public void deleteById(ID id) {
@Transactional
public <S extends T> S save(S entity) {
Maciej Marczuk
  • 3,593
  • 29
  • 28
  • 4
    In spring-data-jpa:2.0.9, there is no `@Transactional` annotation in the source code of `JpaRepository` or its ancestors - the default transactionality seems to be applied at runtime. Also note that if you put `@Transactional( ... custom properties ... )` on your repository interface, it will apply to all methods declared in your interface, and in child interfaces -- but not to any methods declared in the parent interface (`JpaRepository`) unless you redeclare them. – Andrew Spencer Oct 12 '18 at 15:00
  • 5
    @AndrewSpencer `@Transactional` annotations are applied in default `JpaRepository` / `CrudRepository` implementation: `SimpleJpaRepository` Take a look here: https://github.com/spring-projects/spring-data-jpa/blob/master/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java I think you will find there all the answers :) – Maciej Marczuk Mar 12 '19 at 14:52
  • thanks for the correction - it's not runtime magic, simply the implementation class that's annotated. My remark about overriding is still accurate, though. – Andrew Spencer Mar 13 '19 at 13:32