This question already has answers here:
Spring #Transaction method call by the method within the same class, does not work?
(9 answers)
Closed 2 years ago.
I was testing some code and found an interesting scenario.
Scenario:
public class ServiceA {
public List<Object> saveAndGetAllV1() {
serviceB.saveAll();
return getAll();
}
public List<Object> saveAndGetAllV2() {
serviceB.saveAll();
return serviceB.getAll();
}
#Transactional(propagation = Propagation.MANDATORY)
public List<Object> getAll() {
repository.findAll();
}
}
public class ServiceB {
public void saveAll() {
serviceC.saveAll();
}
public List<Object> getAll() {
return repository.findAll();
}
}
public class ServiceC {
public void saveAll() {
repository.saveAll(object);
}
#Transactional(propagation = Propagation.MANDATORY)
public List<Object> getAll() {
return repository.findAll();
}
}
The method saveAndGetAllV1() does not give any error even when the transaction is mandatory in serviceA.getAll() method. While on the other hand saveAndGetAllV2() gives error as serviceC.getAll() requires mandatory transaction.
So my question is why in serviceA.getAll() method the transaction is automatically created but in serviceC.getAll() method the transaction is not automatically created?
The method saveAndGetAllV1() does not give any error even when the
transaction is mandatory in serviceA.getAll() method.
That's because the call of getAll() is a local call and then the #Transactional is not used because the local call is not using the proxy.
Related
I have the following scenario where I have one controller containing two functions (saveAudit and saveProduct). Each one persists an object,I would like to separate transactions between those functions.
throwed exception on saveProduct function should not rollback transaction on saveAudit function :
My repositories/ DAO :
public interface AuditRepository extends JpaRepository<Audit, String> {
}
public interface ProductRepository extends JpaRepository<Product, String> {
}
My controller:
#RestController
#Transactional
public class ProductController {
private final ProductreRepository productRepository;
private final Auditrepository auditRepository;
#Transactional(propagation=Propagation.REQUIRES_NEW)
void saveAudit()
{
auditRepository.saveAudit(Audit.builder().action("action1").build());
}
#PostMapping(ApiPaths.PRODUCTS)
#ResponseStatus(HttpStatus.CREATED)
public ProductDTO addNewProduct() {
ProductDTO res = productRepository.saveProduct(Product.builder().label("product1").build());
saveAudit();
int h=1/0; // => throw exception to rollback product creation
return res;
}
}
Logs:
Participating in existing transaction
its same class proxy will not work.
move below method to #Service class and inject in your controller or annotate #Transactional(propagation=Propagation.REQUIRES_NEW) in auditRepository.saveAudit
#Transactional(propagation=Propagation.REQUIRES_NEW)
public void saveAudit()
{
auditRepository.saveAudit(Audit.builder().action("action1").build());
}
I have the following pseudo code:
#Bean
public List<BeanB> beanB(
List<BeanA> beansA) {
List<BeanB> beansB = new ArrayList<>();
for (BeanA beanA : beansA) {
beansB.add(new BeanB(beanA))
}
return beansB;
}
#Bean
public BeanC beanC(
List<BeanB> beansB) {
return new BeanC(beansB);
}
Now the challenge is when the list of BeansB is constructed post construct is not invoked on those beans in the list. Is there any idiomatic way to trigger post construct invocation on those beans.
This does not work well as #PostConstruct is called by Spring. and from your code, for List<BeanB>, Spring will only try to find #PostConstruct in List. If it can find it, it will execute the #PostConstruct code.
I suspect you are writing beanA as follow in case you have #PostConstruct in beanA's class like below.
#Bean
public BeanA beanA_1() {
return new BeanA();
}
#Bean
public BeanA beanA_2() {
return new BeanA();
}
// which naming List of Bean B as beanB does not seems a good idea though
#Bean
public List<BeanB> beanB(List<BeanA> beansA) {
List<BeanB> beansB = new ArrayList<>();
for (BeanA beanA : beansA) {
beansB.add(new BeanB(beanA))
}
return beansB;
}
To make it work, there are multiple ways, I will just suggests some of them.
Creating a new Class, says BeanBCollectionWrappe to wrap the List of BeanB. And to make the code a bit more robust, I am implementing InitializingBean instead of #PostConstruct which is essentially the same, but allow me to ensure the method is afterPropertiesSet but not any methods.
public class BeanBCollectionWrapper implements InitializingBean {
private List<BeanB> beansB;
public void afterPropertiesSet() throws Exception {
for (BeanB beanB: beansB) {
beanB.afterPropertiesSet();
}
}
// getter and setter
}
public class BeanB implements InitializingBean {
public void afterPropertiesSet() throws Exception {
// ...
}
}
The Wrapper code can definitely be a bit better by use of Generics, like replacing BeanB to <T extends InitializingBean>.
And for the last Part,
#Bean
public BeanC beanC(BeanBCollectionWrapper wrapper) {
return new BeanC(wrapper.getBeansB());
}
Another method can be done by implementing BeanPostProcessor.
For example,
#Component
public class BeanBListBeanPostProcessor implements BeanPostProcessor{
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("beanB")) {
List<BeanB> beanBList = (List) bean;
for (BeanB beanB : beanBList) {
try {
beanB.afterPropertiesSet();
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
return null;
}
}
public class BeanB implements InitializingBean {
public void afterPropertiesSet() throws Exception {
// ...
}
}
I have a controller class which further calls service class method. An AOP #Around aspect is applied on the service class method.
package com.hetal.example;
#RestController
public class CustomerController {
#Autowired
CustomerService customerService;
#RequestMapping(value = "/getDetails", method = RequestMethod.GET)
public String getCustomerDetails() {
System.out.println("Inside controller class");
String details = customerService.getDetails(custName);
System.out.println("Customer details is = " + details); // prints null
}
}
package com.hetal.example;
#Service
public class CustomerServiceImpl implements CustomerService {
#Override
public String getDetails(String custName) {
//some code
returns "Customer details";
}
}
An aspect is written to be executed #Around the method getDetails() of CustomerServiceImpl
package com.hetal.config;
public class JoinPointConfig {
#Pointcut(value="execution(* com.hetal.example.CustomerService.getDetails(..) && args(custName)"))
public void handleCustomerDetails(String custName) {}
}
package com.hetal.config;
#Aspect
#Component
public class CustomerAspect {
#Around("com.hetal.config.JoinPointConfig.handleCustomerDetails(custName)")
public Object aroundCustomerAdvice(ProceedingJoinPoint joinpoint, String custName) {
System.out.println("Start aspect");
Object result= null;
try {
result = joinpoint.proceed();
System.out.println("End aspect");
}
catch(Exception e) {}
return result;
}
}
Execution goes as below,
Controller calls CustomerServiceImpl.getDetails method.
CustomerAspect is called, prints "Start aspect". //before advice
joinpoint.proceed() calls actual CustomerServiceImpl.getDetails method.
CustomerServiceImpl.getDetails returns a string "Customer details" and control comes back to the aspect, prints "End aspect" //after returning advice
Control goes back to controller class but the response received is null.
I want the response returned from the service class into the controller class after the completion of the aspect.
Thank you in advance !!
Yeah you some compilation issue in your applications make those changes and with the belwo return type issue in Aspect class,
but the main issue is with your Aspect class, its void return type hence that coming as null you should return the result as object , below is the code
package com.hetal.config;
#Aspect
#Component
public class CustomerAspect {
#Around("com.hetal.config.JoinPointConfig.handleCustomerDetails(custName)")
public Object aroundCustomerAdvice(ProceedingJoinPoint joinpoint, String custName) {
System.out.println("Start aspect");
Object result= null;
try {
result = joinpoint.proceed();
System.out.println("End aspect");
}
catch(Exception e) {}
return result;
}
}
I am new to Spring Boot and Mockito and having a problem mocking out a repository call in my service test.
I have a "delete" service method call as follows that I am trying to test with Mockito by mocking out the repository calls:
public interface IEntityTypeService {
public EntityType getById(long id);
public EntityType getByName(String name);
public List<EntityType> getAll();
public void update(EntityType entityType);
public void delete(long id);
public boolean add(EntityType entityType);
}
#Service
public class EntityTypeServiceImpl implements IEntityTypeService {
#Autowired
private EntityTypeRepository entityTypeRepository;
#Override
public void delete(long id) {
entityTypeRepository.delete(getById(id));
}
#Override
public EntityType getById(long id) {
return entityTypeRepository.findById(id).get();
}
....implementation of other methods from the interface
}
My repository looks as follows:
#RepositoryRestResource
public interface EntityTypeRepository extends LookupObjectRepository<EntityType> {
}
I have not implemented any of the methods in the repository as I am letting Spring Boot wire it up for me.
My test is as follows:
#RunWith(SpringRunner.class)
public class EntityTypeServiceTest {
#TestConfiguration
static class EntityTypeServiceImplTestContextConfiguration {
#Bean
public IEntityTypeService entityTypeService() {
return new EntityTypeServiceImpl();
}
}
#Autowired
private IEntityTypeService entityTypeService;
#MockBean
private EntityTypeRepository entityTypeRepository;
#Test
public void whenDelete_thenObjectShouldBeDeleted() {
final EntityType entity = new EntityType(1L, "new OET");
Mockito.when(entityTypeRepository.findById(1L).get()).thenReturn(entity).thenReturn(null);
// when
entityTypeService.delete(entity.getID());
// then
Mockito.verify(entityTypeRepository, times(1)).delete(entity);
assertThat(entityTypeRepository.findById(1L).get()).isNull();
}
}
When I run the test, I get an error saying "java.util.NoSuchElementException: No value present"
java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at xyz.unittests.service.EntityTypeServiceTest.whenDelete_thenObjectShouldBeDeleted(OriginatingEntityTypeServiceTest.java:41)
It references the line in the test saying Mockito.when(originatingEntityTypeRepository.findById(1L).get()).thenReturn(entity).thenReturn(null);
The reason I think I have to mock that call out is because the delete method in the Service calls the getById() method in the same service, which in turn calls entityTypeRepository.findById(id).get()
It is that, that I am assuming I have to mock out on the delete. But clearly I am wrong. Any assistance would be appreciated.
Many thanks
#Test
public void whenDelete_thenObjectShouldBeDeleted() {
final EntityType entity = new EntityType(1L, "new OET");
Optional<EntityType> optionalEntityType = Optional.of(entity);
Mockito.when(entityTypeRepository.findById(1L)).thenReturn(optionalEntityType);
// when
entityTypeService.delete(entity.getID());
// then
Mockito.verify(entityTypeRepository, times(1)).delete(entity);
//I dont think you need to assert to confirm actual delete as you are testing mock registry. to assert somethink like below you need to return null by mocking the same call again and return the null but thats of no use
//assertThat(entityTypeRepository.findById(1L).get()).isNull();
}
Updated your test. Basically we first need to mock the result of findById. refer my comment above asserting the actual delete.
We have a scenario where the dependency should be determinded at runtime. An example shows the detail below:
public class OrderProcessor {
// The Validator should be determinded based on the version of the service.
private Validator orderProcessValidator;
public Confirmation process(Order order) {
if(orderProcessValidator.validate(order)) {
// Business logic
}
}
}
Is possible to inject Validator dynamically with Spring IOC, or can only be solved through the factory pattern?
It is still a bit unclear for me your scenario, but I am assuming you have two actual Order classes in your project. Maybe one is in com.foo.api1 package and the other is in com.foo.api2 package. Or one Order is called Order1 and the other is called Order2. The idea is that I'm assuming you have two different classes for the two 'api versions' of Order.
You can achieve what you need by using Spring AOP:
#Aspect
#Component
public class MyAspect {
#Autowired
private Validator1 validator1;
#Autowired
private Validator2 validator2;
#Pointcut("execution(* com.foo.bar.OrderProcessor.process(..))")
private void myPointcut() {}
#Around("myPointcut() && args(order)")
public Object myAroundAdvice(ProceedingJoinPoint pjp, Object order) throws Throwable {
if (order instanceof Order1) {
validator1.validate((Order1) order);
} else
if (order instanceof Order2) {
validator2.validate((Order2) order);
}
Object retVal = pjp.proceed();
return retVal;
}
}
#Component
public class OrderProcessor {
public void process(Object order) {
System.out.println("processing order");
}
}
#Component
public class Validator1 {
public void validate(Order1 order) {
System.out.println("validating inside validator 1");
}
}
#Component
public class Validator2 {
public void validate(Order2 order) {
System.out.println("validating inside validator 2");
}
}
So, basically, you are defining an aspect that should intercept calls to your OrderProcessor class and, depending on what parameter it receives, it calls one validator or the other.