Spring #Transactional different behavior on Service and non-Service methods? - java

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

Related

Proper Hibernate nested transactions handling

I am sure that I am missing something, but I don't know exactly what...
Giving the following snippet:
#Service
public class MyClass {
private MyClass self;
private UserRepository userRepository;
private ApplicationContext applicationContext;
#PostConstruct
private void init() {
self = applicationContext.getBean(MyClass.class);
}
#Transactional
public void doA(User user) {
...
if (condition) {
self.doB(user);
throw new SecurityException();
}
user.setRandomField("x");
userRepository.save(user);
}
#Transactional(value = Transactional.TxType.REQUIRES_NEW)
public void doB(User user) {
...
userRepository.save(user);
}
}
What do I know about #Transactional is that if it is used, is redundant to call repository.save(entity).
What I am trying to do, is to process an entity from a transactional method, and if there is a breaking condition, call a new method (annotated with REQUIRES_NEW) that will update some fields of the entity and save it. The root method (doA) then throws an exception. FYI: the #Transactional(dontRollbackOn = SecurityException.class) is not an option in this situation.
For using this commiting mechanism, instead of creating a new bean just with one method I just injected the current bean into a variable just called self, therefore I can use the bean proxy for transaction management.
The odd thing is that if I am removing from doB the save call, when doA transaction is rollbacked because of the SecurityException, the changes performed by doB are rollbacked as well. But if I let it in there, this is working as expected.
Am I doing something wrong or am I missing something?
Thanks!
Try to do not pass User instance in the doB().
Pass an Id instead and read the User from the repo internally. I am not sure how the attached entity is handled between the different sessions.

Why does save method needs to use an already existing EntityManager?

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.

Spring Boot With #Transactional annotated method, calls other transactional methods and throws an Exception

I am trying to process a record within a #Transactional annotated method. There are some dependencies from other methods that must be taken care before processing some other secondary (but necessary) business logic. The main record seems to be saved with no problem inside the transaction, but when I try to clean some data by using another service, it throws a JpaEntityNotFoundException.
I used Propagation.REQUIRES_NEW in the method that causes the issue, but with no success. Does anyone have an idea about what I am missing or doing wrong? I must not commit the main record before doing the rest of the transactional operations.
The exception that I am getting is: org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.myproject.repository.entity.Book with id 5851445; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.myproject.repository.entity.Book with id 5851445...............
Here is an example that shows somehow my issue:
ServiceA class
#Service
public class ServiceA {
public void nonTransactionalMethodA(Book book) {
//..... Any independent logic from ServiceB
updateAuthor(book);
nonTransactionalMethodB();
}
public void nonTransactionalMethodB() {
//post process logic ......
}
}
ServiceB Class
#Service
public class ServiceB {
#Autowired
private BookRepository bookRepository;
#Autowired
private OfferRepository offerRepository;
#Transactional
private void updateAuthor(Author author) {
Book book = new Book(1);
book.setAuthor(author);
bookRepository.save(book);
removeUnavailableOffers(book);
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void removeUnavailableOffers (Book book) {
/*throwing org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.myproject.repository.entity.Book with id 5851445; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.myproject.repository.entity.Book with id 5851445............*/
offerRepository.deleteByBookIdIsNullAndBookAuthorIdNot(book.authorId);
}
}
Any thought on this will be greatly appreciated.
Make sure that the method annotated with #Transactional is declared as public and called by a different class.
A transactional method must be public so that Spring can override it and it must be called from outside the defining class so that the invocation can go through a proxy.
It's one of the common pitfalls when using #Transactional, for more information see https://codete.com/blog/5-common-spring-transactional-pitfalls/

Propagate spring transaction to sibling call

Consider I have following spring beans
CompositeService:
#Service
public class CompositeService {
#Resource
private ServiceA serviceA;
#Resource
private ServiceB serviceB;
public ResultBean compositeMethod() {
ResultBean result = new ResultBean();
result.setA(serviceA.getA());
result.setB(serviceB.getB());
return result;
}
}
ServiceA:
#Service
public class ServiceA {
#Transactional
#Cacheable
A getA() {
// calls DAO layer and makes a query to the database
}
}
ServiceB:
#Service
public class ServiceB {
#Transactional
#Cacheable
B getB() {
// calls DAO layer and makes a query to the database
}
}
The cacheable aspect has a higher order
The problem with this code is that it will start two transactions (and take two connections from the pool) in case of cache-miss in both services.
Can I configure Spring to use the same transaction in this use case?
I.E. propagate the transaction from ServiceA to the CompositeService and after that down to ServiceB?
I cannot set the CompositeService as transactional because I do not want to start a transaction (and borrow a connection from pool) in case of cache-hit both in ServiceA and ServiceB
Spring will only propagate a transaction if everything is under the same transaction. So the short answer is that you should annotate your CompositeService with #Transactional.
#Service
public class CompositeService {
#Transactional
public ResultBean compositeMethod() {
ResultBean result = new ResultBean();
result.setA(serviceA.getA());
result.setB(serviceB.getB());
return result;
}
}
Generally this is fast enough as it only does a checkout from the underlying connection pool. However if you experience latency or don't always need a connection you can wrap your actual DataSource in a LazyConnectionDataSourceProxy. This will obtain a Connection when first needed.
What you can do is you can annotate the compositeMethod with #Transactional as well. The default propagation level of a transaction is set to Required
Support a current transaction, create a new one if none exists.
So even though its not exactly what you asked, the transaction demarcation starts with the compositeMethod, but it should be exactly the semantics you want.

Please explain the #Produces annotation in CDI

I have read about the #Produces annotation in CDI, but I don't understand its usage.
public class Resources {
// Expose an entity manager using the resource producer pattern
#SuppressWarnings("unused")
#PersistenceContext
#Produces
private EntityManager em; //
#Produces
Logger getLogger(InjectionPoint ip) { //
String category = ip.getMember()
.getDeclaringClass()
.getName();
return Logger.getLogger(category);
}
#Produces
FacesContext getFacesContext() { //
return FacesContext.getCurrentInstance();
}
}
taken from: http://www.jboss.org/jdf/quickstarts/jboss-as-quickstart/guide/GreeterQuickstart/#GreeterQuickstart-
How does the container know to call a producer method? If I inject an EntityManager, how does the container call the #produces EntityManager? And how would a getLogger producer method get called?
I also don't see the reason to go through all of the trouble.
Section 3.3 of the CDI specification gives a pretty good high level overview of the use of the #Produces annotation:
A producer method acts as a source of objects to be injected, where:
• the objects to be injected are not required to be instances of beans, or
• the concrete type of the objects to be injected may vary at runtime, or
• the objects require some custom initialization that is not performed by the bean constructor.
Let's say, for example, that you wanted to bridge between a Java EE managed component like an entity manager and other CDI components, you could utilize the #Produces annotation. Another benefit being that you avoid having to duplicate #PersistenceContext annotations throughout your data domain layer.
class A {
#PersistenceContext // This is a JPA annotation
#Produces // This is a CDI 'hook'
private EntityManager em;
}
class B {
#Inject // Now we can inject an entity manager
private EntityManager em;
}
Another handy use is for getting around libraries that do not have CDI friendly beans (for example, no default constructors):
class SomeTPLClass {
public SomeTPLClass(String id) {
}
}
class SomeTPLClassProducer {
#Produces
public SomeTPLClass getInstance() {
return new SomeTPLClass("");
}
}
The Javadoc for produces also shows an interesting (but fairly rare case) of producing a named collection that can later on be injected into other managed beans (very cool):
public class Shop {
#Produces #ApplicationScoped
#Catalog #Named("catalog")
private List<Product> products = new LinkedList<Product>(8);
//...
}
public class OrderProcessor {
#Inject
#Catalog
private List<Product> products;
}
The container is responsible for processing all methods and fields marked with a #Produces annotation, and will normally do this when your application is deployed. The processed methods and fields will then be used as part of the injection point resolution for managed beans, as needed.
The example didn't quite work for me. What dit work was a minor tweak:
#Alternative
class SomeTPLClass {
public SomeTPLClass(String id) {
}
}
class SomeTPLClassProducer {
#Produces
public SomeTPLClass getInstance() {
return new SomeTPLClass("");
}
}
So i had to add #Alternative on my class to get rid of the error that there were two options for #Default.

Categories

Resources