I have a Spring Boot 2.3 REST application with a standard architecture (controller -> service -> repository). For auditing purposes, I inserted a thin layer (some kind of a Mediator), so that I persist all the requests to some specific service method regardless they are successfully persisted or an exception is thrown and the transaction is rollbacked. Example:
#Component
public class Mediator {
private final Service service;
private final AuditService auditService;
public Mediator(Service service, AuditService auditService) {
this.service = service;
this.auditService = auditService;
}
#Transactional
public void saveReport(Report report) {
try {
service.saveReport(report);
auditService.saveReport(report);
} catch (Exception exception) {
auditService.saveReport(report, exception);
throw exception;
}
}
}
Thus I encountered a weird situation: if I place the #Transactional on the Mediator's method (example above), all the operations in the JPA Repositories are successfully persisted. If I place the #Transactional on the ServiceImpl method instead (example below) and no longer on the Mediator, one of the delete queries is not ran, although the rest of the queries are executed just fine. Suppose my ServiceImpl looks something like:
#Service
public class ServiceImpl implements Service {
private final RepositoryA repositoryA;
private final RepositoryB repositoryB;
public ServiceImpl(RepositoryA repositoryA, RepositoryB repositoryB) {
this.repositoryA = repositoryA;
this.repositoryB = repositoryB;
}
#Transactional
public void saveReport(Report report) {
repositoryA.save(report.getA());
repositoryB.save(report.getB());
repositoryB.delete(report.getSomethingElse());
}
}
The only visible difference between the two approaches with respect to the Transactions is that in the first scenario, I have this for each Mediator call:
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(909894553<open>)] for JPA transaction
while in the second scenario, I have this:
tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
I guess there is a difference between annotating directly a bean's method with #Transactional (what I do in the Mediator) and annotating a bean's (that is the implementation of the interface injected) method with the same thing (what we usually do by annotating a ServiceImpl method), but I am not sure and cannot spot the reason for this weird behaviour. Does anyone have an idea why this happens?
I guess that this difference in behavior is due to Spring OpenSessionInView which is enabled by default.
You must set in application.yml
spring:
jpa:
open-in-view: false
Please see OSIV
Need some help here, I'm not able to understand why my transactions are not getting rolled back in an event of exception.
I will try to put my code as close to as It is on the project (cannot share on the internet)
This is my Service
#Service
#SL4j
#Transactional(propagation = propagation.SUPPORTS, readOnly=true)
public class PublicationServiceImpl implements PublicationService{
#Autowired
private PartnerRepo partnerRepo;
#Autowired
private FlowRepo flowRepo;
#Autowired
private PubRepo pubRepo;
#Override
#Transactional(propagation = propagation.REQUIRED, rollbackFor=Exception.class)
public int save(Request request) {
try{
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode= pubRepo.save(pk_id_partner, pk_id_flow, request);
}
catch(Exception e){
log.error("Exception in saving");
}
return 0;
}
}
This is my Repository (example of 1 , all 3 repos follow same coding standards)
#Repository
#Slf4j
public class PartnerRepo implemets PartnerRepo{
#Autowired
private NamedParamaterJDBCTemplate namedParamaterJDBCTemplate;
//String Declarations .....
private MapSqlParameterSource sqlParameterSource;
#Override
public int save(Request request){
sqlParamatersSource = new MapSqlParameterSource();
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
return executeQuery();
}
private int executeQuery(){
try{
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource , kekHolder, new String[]{"pk_id"})
return keyHolder.getKey().intValue();
}catch(Exception e){
log.error("Exception while saving");
return 0;
}
}
}
So the problem is , Consider there is an exception in the method generatePubCode(request); , ideally since I have used #Transactional at class level and method level , The previous 2 repo transactions () should be rolled back right? However it isn't happening, Even After the code is finished execution I can see the records in DB (Postgres DB v10).
Please help figure out this issue , Am I doing something fundamentally wrong ?
Please do let me know in case you need further information that might help here!
P.S: I have tried all permutations of #Transactional , nothing works : ONLY having this in the catch block works! TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
I wonder If its the right approach for a springBoot project
Thanks in advance for the help!
Edit: as per suggestion made the PublicationServiceSaverImpl.save() public
Best reagards,
Bhargav.
There are several things that break proper transactions in Spring
Your service method is private
You are catching and swallowing exceptions
private method
The fact that your PublicationServiceImpl save method is private basically makes the #Transactional on that method useless. As a private method cannot be proxied, no transactions will apply. Even if it would be public it wouldn't work as you are calling the method from within the same object, hence the transactionality of that method applies.
To fix, make your method public and call the save method from an other class (or make the actual method that is calling save have the proper #Transactional.
The fact that is doesn't work is due to the type op AOP being used, by default Spring will use proxies and this is a drawback of using proxy based AOP.
Another solution to make it work with private methods is to switch to full-blown AspectJ with either compile-time or load-time weaving of the classes. Both require additional setup and that can be tedious.
Catch and swallow exceptions
You have in both your repository as well as your service a try/catch block. Each of those catches and swallows the exceptions (they are logged but not re-thrown).
For transactions to work properly it needs to see the exceptions. The fact that you are catching and swallowing them, makes the transaction aspect not see them and instead of doing a rollback, do a commit. For the transaction aspect everything is ok because there was no exception.
To fix, remove either the try/catch or rethrow the exceptions.
Annotations in general never work on methods called from the same class because of how proxies are created in Spring.
It has nothing to do with #Transaction in particular but with the fact that your methods is private and called from the within same object.
Please make the method public and move the #Transactional method in a separate class annoted with #Service and called it from outside of the instance of the class
#Service
public class PublicationServiceSaverImpl {
#Transactional
**public** int save(Request request) {
...
}
}
You must call the save method from outside of the class PublicationServiceSaverImpl, maybe from PublicationServiceImpl.
The method PublicationServiceImpl.save must be public if you want to use #Transactional.
As per Spring Documentation:
When you use transactional proxies with Spring’s standard configuration, you should apply the #Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the #Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
First of all: make your method public.
Second: you have to throw the exception. If you catch and not rethrow it, how do you expect the transactional processing to know that an error occured and then rollback?
You have two options: throw the Exception instead of catching it, or catch, do some further processing and then rethrow it.
So in your repository, just add a throws keyword and then rethrow the exception after the log statement:
public int executeQuery() throws Exception {
try {
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource, kekHolder, new String[] {
"pk_id"
})
return keyHolder.getKey().intValue();
} catch(Exception e) {
log.error("Exception while saving");
throw e;
}
}
Now, for your service:
Example 1 - use the throws keyword to propagate the checked exception:
#Override
#Transactional(propagation = propagation.REQUIRED, rollbackFor = Exception.class)
public int save(Request request) throws Exception {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
return 0;
}
Example 2 - catch and rethrow it as an RuntimeException, which is unchecked.
#Override
#Transactional(propagation = propagation.REQUIRED)
public int save(Request request) {
try {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
return 0;
}
Note that the second example doesn't need the rollbackFor argument to the #Transactional. By default, a transaction is rolled back if a unchecked exception occurs, so there's no need to explicitly use rollbackFor in cases of RuntimeExceptions.
Another verification that would have to be done if the solution does not work. It is to verify that the database tables allow the rollback. For this, the engine has to be in InnoDB and not in MyISAM and others.
In my case adding #EnableTransactionManagement annotation on Application class resolved the issue
In stead of #Transactional(propagation = propagation.REQUIRED provide #Transactional(propagation = propagation.REQUIRED_NEW
If you use the latter, it will use the parent transaction boundary, which is at class level.
And you don't need explicitly state rollbackFor=Exception.class. By default spring will roll back on exception
And do change private to public
Try this out
Recently I faced an issue regarding updating entities from a #Scheduled method where it would fail with the exception org.hibernate.TransientPropertyValueException: object references an unsaved transient instance even though it would work seamless when invoked from a #RestController method. This is the relevant example:
The offending method (other parts of the class omitted for brevity):
#Service
public class AnonymizationService
{
private final ItemRepository itemRepository;
public Result anonymizeItemsOlderThan(int days) {
List<Item> data = itemRepository.findAllByCreatedDateBeforeAndAnonymizationDateIsNull(Instant.now().minus(days, ChronoUnit.DAYS));
List<String> itemsAnonymized = new ArrayList<>(data.size());
data.forEach(item -> itemsAnonymized.add(itemRepository.save(item.anonymize()).getRequestId()));
return Result.builder().anonymizedItems(itemsAnonymized).build();
}
}
The #RestController caller (again most stuff omitted):
#RestController
public class DataAnonymizationAPI
{
private final AnonymizationService anonymizationService;
#PutMapping(path = "${datadeletion.path:/anonymize}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Result> anonymizeAll(#Valid DataDeletionRules dataDeletionRules) {
return ResponseEntity.ok(anonymizationService.anonymizeItemsOlderThan(dataDeletionRules.getMinimunAge()));
}
}
Again, this works just fine when used like above. The problem happens when AnonymizationService#anonymizeItemsOlderThan() is instead invoked from the following #Scheduled method:
#Component
public class DataDeletionTasks
{
private final AnonymizationService anonymizationService;
private final DataAnonymizationProperties properties;
#Scheduled(cron = "${datadeletion.anonymization.schedule}")
public void anonymizeItemsPeriodically() {
anonymizationService.anonymizeItemsOlderThan(properties.getAnonymization().getMinAge());
}
}
In this case it fails with the exception mentioned above (org.hibernate.TransientPropertyValueException).
Upon changing the log level to DEBUG and carefully analyzing it, nothing unexpected happens:
When the method is invoked from the #RestController an existing EntityManager is used and a transaction created:
o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1702787226<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
When the method is invoked from the #Scheduled method a new EntityManager is created:
o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(644498403<open>)] for JPA transaction
Naturally, my instinct was to add #Transactional to the Anonymization#anonymizeItemsOlderThan() method which immediately solved it, but why?
Why does it work in one case and not in the other? Why does the saveAndFlush() must be performed using the same EntityManager used to retrieve the entity in the first place?
This situation made me think my knowledge is flawed on a very basic level, but somehow couldn't find a clear explanation to it. In any case feel free to point me towards relevant literature that might help me.
I am trying to test the mechanism of rollbackin transaction in cases of somthing went wrong. I read many similar topics, but nothing helps me. Here is what i try:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/rest-servlet.xml" })
#Transactional
public class TagDaoImplTest extends DatabaseTestCase {
private static final String FLAT_XML_DATASET = "FlatXmlDataSet.xml";
#Autowired
private TagDao tagDao;
#Autowired
private SessionFactory sessionFactory;
#Before
public void setUp() throws Exception {
DatabaseOperation.REFRESH.execute(getConnection(), getDataSet());
}
#Test
public void testAddWithRollback() throws Exception {
addWithRollback("testTagToAdd"); //i suppouse that we tried to add, but somthing went wrong and transaction was rolled back
final Tag testTagToAdd = tagDao.findByTag("testTagToAdd"); // so db must be without changes and i check this
assertNull(testTagToAdd);
}
#Rollback(true) //i want this method to rollback after its work to imitate corrupted transaction
private void addWithRollback(String tag) throws Exception {
tagDao.add(new Tag(tag));
}
}
My dao looks like this:
#Repository("tagDao")
public class TagDaoImpl implements TagDao {
#Override
public void add(Tag tag) {
final Session session = sessionFactory.getCurrentSession();
session.persist(tag);
}
}
But my test fails because it finds that tag in db (and this means transaction wasn`t rolled back). I tried many different things like get current session and transaction and manually invoke rollback, but nothing happend. Can you please help me?
First of all, for me it feels a bit weird that you try to test the repository layer. Normally there should not be any business logic, thus this means that you try to test Hibernate or SQL which are already tested million of times and there is no point of doing it.
I would suggest to annotate your method with something like this:
#Transactional(rollbackFor=Exception.class)
and maybe specify the Exception. Then in the test you prepare your system in a way that this method throws this exception. Thus it should be rolled back and no data should be changed.
Furthermore, I would like to add that actually loading spring context at this point most probably is different compare to the production one. Thus this is an additional point where I would say that there is no point of doing it. At least on the repository layer.
Due to lack of key words to capture this scenario, let me just proceed to describe it. The classes have been simplified.
Given this:
public ItemController {
#Autowired
ItemDtoService ItemDtoService;
#Autowired
DiscountService discountService;
#RequestMapping(value = "/viewItems", method = RequestMethod.POST)
public void process() {
List<ItemDto> ItemDtos = ItemDtoService.getItemDtos();
for(ItemDto i: ItemDtos) {
boolean isDiscounted = discountService.hasDiscount(i); //throws exception here on iteration 2 and the last iteration, ItemDto was discounted
if (isDiscounted) {
i.setPrice(discountService.getDiscountedPrice(i));
//do some other i.setter, basically modify the pojo
}
}
}
}
An exception is thrown at the discountService.hasDiscount when:
on subsequent iteration
and the previous iteration, the ItemDto was discounted.
Exception is:
Caused by: org.hibernate.exception.SQLGrammarException: could not update: [somepackage.ItemDto#364]
And somewhere in the stacktrace you will see this:
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:456)"
The problem is that method call uses a dao method underneath that is #Transactional (and maybe for a good reason even though it's only a query, complicated query). When the JPA Tx manager does its job upon method call end, it sees the pojo as modified and tries to synch it. The ItemDto pojo does have #Entity because inside ItemDtoService.getItemDtos uses the getEntityManager().createNativeQuery(nativeSql, ItemDto.class). The 5 other class details are here:
#Entity
public class ItemDto{
//body
}
#Service
public class ItemService {
#Autowired
ItemDao itemDao;
public List<ItemDto> getItems() {
return itemDao.getItems(); //for sake of simplicity
}
}
#Repository
#Transactional
public class ItemDaoImpl {
public List<ItemDto> getItems() {
String nativeSql = "select...."
return getEntityManager().createNativeQuery(nativeSql, ItemDto.class);
}
}
#Service
public class DiscountService {
#Autowired
DiscountDao discountDao;
public boolean hasDiscount(ItemDto i) {
boolean hasDiscount = discountDao.hasDiscount(i);
//do other service stuff that might influence the hasDiscount flag
return hasDiscount;
}
}
#Repository
#Transactional
public class DiscountDaoImpl {
public boolean hasDiscount(ItemDto i) {
String nativeSql = "select...."
boolean hasDiscount;
//in reality the query is a complicated joins, executes and returns if has discount or not
return hasDiscount;
}
}
What am I doing wrong?
Some of the options I tried and worked include:
add to the #Transactional the (readonly=true) on the Dao methods
since they are only queries (negative effect though is those might
be intentionally transactional due to complex queries, and may need
locking to prevent dirty reads)
in the Controller, create a separate loop for modification, it
then have 2 loops, 1 for looping through items and seeing which is
discounted, store those info somewhere to be referenced later on 2nd
loop, which does the modification of said pojos
I am looking at other options, and please comment if you see something wrong with the way it was coded.
Another option I just found is inside the Dao that returns the list of ItemDto, before returning the list, I would execute this:
getEntityManager().clear();
It works fine because the list is Dto anyways and one would expect that these require no DB synching, at the same time the #Transactional is retained for necessary locking for consistent reads.
That's one more alternative, but what is the most appropriate way really?