I have a project annotation-configured in Spring Boot. The pom have the dependencies spring-data-jpa and spring-tx.
Nothing about transaction configuration in Config class or properties (cause teorically is no needed).
Given this service:
package com.appserver.motion.service;
@Component(value = "exerciseService")
@Service
@Data
public class ExerciseService {
@Autowired private ExerciseRepository exerciseRepository;
@Autowired private TagRepository tagRepository;
@Autowired private ExerciseTagJoinRepository exerciseTagJoinRepository;
@Autowired private EquipmentRepository equipmentRepository;
@Autowired private ExerciseEquipmentJoinRepository exerciseEquipmentJoinRepository;
private ExerciseCommand exerciseCommand = new ExerciseCommand();
private List<SelectItem> tags = new ArrayList<SelectItem>();
private List<Integer> selectedTags = new ArrayList<Integer>();
private List<SelectItem> equipments = new ArrayList<SelectItem>();
private List<Integer> selectedEquipments = new ArrayList<Integer>();
@Transactional
public int saveExercise() {
try {
int resultat = exerciseRepository.save(toEntity()).getExe_id();
if (this.selectedTags!=null) {
for (Integer selectedTag: this.selectedTags) {
ExerciseTagJoinEntity etje = new ExerciseTagJoinEntity();
etje.setExa_exe_id(new Integer(resultat));
etje.setExa_tag_id(new Integer(selectedTag));
exerciseTagJoinRepository.save(etje);
}
}
if (this.selectedEquipments!=null) {
for (Integer selectedEqu: this.selectedEquipments) {
ExerciseEquipmentJoinEntity eeje = new ExerciseEquipmentJoinEntity();
eeje.setEeq_exe_id(new Integer(resultat));
eeje.setEeq_equ_id(new Integer(selectedEqu));
exerciseEquipmentJoinRepository.save(eeje);
}
}
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Info:", "Insertat correcte");
FacesContext.getCurrentInstance().addMessage("Correcte:", facesMsg);
return resultat;
}catch (Exception ex) {
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error:", ex.getMessage());
FacesContext.getCurrentInstance().addMessage("Error:", facesMsg);
return -1;
}
}
}
The repositories are all extensions of JpaRepository and annotated with @Repository.
The problem is that the transacction not rollback on exceptions, with or without the rollBackFor attribute.
On the other hand, the transacction seems to be working because the inserts are not flushed in DB until the end of the save method.
I'm using PostgreSQL DB by the way.
Is something i'm missing?
I have been logging the transacction packages and this is what i get:
2019-11-15 08:52:45.493 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Getting transaction for [com.appserver.motion.service.ExerciseService.saveExercise] 2019-11-15 08:52:47.678 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 2019-11-15 08:52:48.015 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 2019-11-15 08:52:49.163 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 2019-11-15 08:52:49.168 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 2019-11-15 08:52:49.168 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 2019-11-15 08:52:49.171 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save] 2019-11-15 08:53:02.439 TRACE 9100 --- [nio-3000-exec-4] o.s.t.i.TransactionInterceptor : Completing transaction for [com.appserver.motion.service.ExerciseService.saveExercise]
Your @Transactional
method does not throw an exception because you catch and handle it and rollback will therefore not happen.
If you want to catch the exception - for logging purposes for example - then you to re-throw the exception. Additionally, rollback will only occur automatically for exceptions extending from java.lang.RuntimeException
.
//if the runtime type of Exception is a checked exception then optional config
@Transactional(rollbackFor = SomeNonRunTimeException.class))
public int saveExercise() {
try {
....
}catch (Exception ex) {
//do some logging
throw ex; //otherwise no rollback
}
}
On the other hand, the transaction seems to be working because the inserts are not flushed in DB until the end of the save method.
This has nothing to do with transactions but is due to the 'write behind' pattern that delays flushing changes to the database until as late as possible - typically on transaction commit i.e. when your @Transactional
method returns
http://learningviacode.blogspot.com/2012/02/write-behind-technique-in-hibernate.html
This pattern leads to another issue - your exception handler will not catch exceptions generated at flush time as the flush happens on transaction commit i.e. on method return - outside of your catch block. If you want to catch and handle these exceptions then you need to do an explicit flush inside your catch block:
@Transactional(rollbackFor = SomeNonRunTimeException.class))
public int saveExercise() {
try {
//explicit flush required if we want to ctach and handle SQL exceptions
exerciseTagJoinRepository.saveAndFlush(etje);
}catch (Exception ex) {
//do some logging
throw ex; //otherwise no rollback
}
}