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 ProjectDependencies 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
|