Close

Using Transactions in Spring Data JPA

[Last Updated: Aug 2, 2018]

In Spring JPA, SimpleJpaRepository is used as the default implementation of CurdRepository. The Spring annotation @Transactional are used in this default implementation (check out basic @Transactional tutorial). The class SimpleJpaRepository itself is annotated with @Transactional(readOnly = true), that means by default all method will run within read only transactions. The write operations override that behavior with a plain @Transaction annotation, which means all those methods will run within write transactions and with other default settings. Following is a quick snippet to have an idea:

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

        @Transactional
         public void deleteById(ID id) {....}
         @Transactional
         public void deleteAll(Iterable<? extends T> entities) {...}
         @Transactional
         public void deleteInBatch(Iterable<T> entities) {...}
         @Transactional
         public void deleteAll() {...}
         @Transactional
         public void deleteAllInBatch() {...}
         public Optional<T> findById(ID id) {...}
         public T getOne(ID id) {...}
         public boolean existsById(ID id) {...}
         public List<T> findAll() {...}
         public List<T> findAllById(Iterable<ID> ids) {...}
         public List<T> findAll(Sort sort) {...}
         public Page<T> findAll(Pageable pageable) {...}

         // other read operations without @Transactional and write operations with @Transactional
}

Overriding repository transaction

To override default transactions settings we can override the desired method like this:

public interface EmployeeRepository extends CrudRepository<Employee, Long> {

    @Transactional(timeout = 10)
    @Override
    public void deleteById(ID id);
}

Using transactions outside of repository

To use transaction outside, we need to use @EnableTransactionManagement on our JavaConfig (for using just repository transactions like above one, we don't need that):

@Configuration
@ComponentScan
@EnableTransactionManagement
public class AppConfig {
 ....
}

Then we can use transactions like this:

  @Service
  public class MyExampleBean{
   @Transactional
    public void saveChanges() {
        repo.save(..);
        repo.deleteById(..);
        .....
    }
}

Example

Entity

@Entity
public class Employee {
  @Id
  @GeneratedValue
  private Long id;
  @Column(unique = true)
  private String name;
  private String dept;
  private int salary;
    .............
}

Repository

package com.logicbig.example;

import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;

public interface EmployeeRepository extends CrudRepository<Employee, Long> {

  @Transactional(timeout = 10)
  @Override
  <S extends Employee> S save(S s);
}

Example Client

@Component
public class ExampleClient {

  @Autowired
  private EmployeeRepository repo;

  public void findEmployees() {
      System.out.println(" -- finding all employees --");
      repo.findAll().forEach(System.out::println);
  }

  @Transactional
  public void saveEmployees() {
      repo.save(Employee.create("Mike", "Sale", 1000));
      repo.save(Employee.create("Diana", "Admin", 3000));
      repo.save(Employee.create("Diana", "IT", 3200));
  }
}

In saveEmployees() method above, we are trying to save employees with duplicate names. As in our Employee entity class we have specified unique column via @Column(unique = true), the last save call will fail and the whole transaction will be rollbacked. If we don't use @Transactional annotation above, first two employees will still be populated i.e. whole safe process will not be atomic.

Also even though the 'save()' method defined inside the repository class is annotated with @Transactional, our call repo.save() inside saveEmployees method won't cause the save method to create another transaction, that's because the default value of @Transactional#propagation is Propagation.REQUIRED which supports the current transaction, if there's none then a new one is created.

JavaConfig

@EnableJpaRepositories
@ComponentScan
@Configuration
@EnableTransactionManagement
public class AppConfig {

  @Bean
  EntityManagerFactory entityManagerFactory() {
      EntityManagerFactory emf =
              Persistence.createEntityManagerFactory("example-unit");
      return emf;
  }

  @Bean
  public PlatformTransactionManager transactionManager() {
      JpaTransactionManager txManager = new JpaTransactionManager();
      txManager.setEntityManagerFactory(entityManagerFactory());
      return txManager;
  }
}

Main class

public class ExampleMain {

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(AppConfig.class);
      ExampleClient exampleClient = context.getBean(ExampleClient.class);
      try {
          exampleClient.saveEmployees();
      } catch (Exception e) {
          System.err.println(e);
      }
      exampleClient.findEmployees();
      EntityManagerFactory emf = context.getBean(EntityManagerFactory.class);
      emf.close();
  }
}
org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: could not execute statement; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
 -- finding all employees --

If we don't use @Transactional on saveEmployees:

@Component
public class ExampleClient {
  ....
  public void saveEmployees() {
      repo.save(Employee.create("Mike", "Sale", 1000));
      repo.save(Employee.create("Diana", "Admin", 3000));
      repo.save(Employee.create("Diana", "IT", 3200));
  }
}

Output in this case:

org.springframework.orm.jpa.JpaSystemException: org.hibernate.exception.ConstraintViolationException: could not execute statement; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
 -- finding all employees --
Employee{id=1, name='Mike', dept='Sale', salary=1000}
Employee{id=2, name='Diana', dept='Admin', salary=3000}

Example Project

Dependencies and Technologies Used:

  • spring-data-jpa 2.0.9.RELEASE: Spring Data module for JPA repositories.
    Uses org.springframework:spring-context version 5.0.8.RELEASE
  • hibernate-core 5.3.3.Final: Hibernate's core ORM functionality.
    Implements javax.persistence:javax.persistence-api version 2.2
  • h2 1.4.197: H2 Database Engine.
  • JDK 1.8
  • Maven 3.5.4

using Transactions in Spring DataJPA Select All Download
  • spring-data-jpa-enabling-transactions
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • EmployeeRepository.java
          • resources
            • META-INF

    See Also