Propagation.REQUIRES_NEW not working properly - spring-boot

I have the following scenario.
I have one transaction method which calls another transaction method which having REQUIRED_NEW Propagation. if the first method gets exception then the second method (REQUIRED_NEW Propagation) also rollbacks.
I am using JPA, Spring-boot and chainedKakfkaTransactionManager
I have tried with changing chainedKakfkaTransactionManager to default one still no luck
here is my code :
#Service
#Transactional(readOnly = false)
public class ComponentServiceImpl implements ComponentService {
#Autowired
private UserRepository userRepository ;
#Override
#Transactional
public boolean validateName(String name) {
try{
retrun userRepository.validate(name);
}catch(Exception e){
handleError(name);
throw new Exception("user not valid");
}
}
#Override
#Transactional(propagation=Propagation.REQUIRES_NEW)
public boolean handleError(String name) {
userRepository.update(name);
}
}
Rollback is happening in the handleError method too. is there any code mistake?

Thanks #DarrenForsythe,
By Creating an autowire object for the same class (self-reference) its worked for me
#Service
#Transactional(readOnly = false)
public class ComponentServiceImpl implements ComponentService {
#Autowired
private UserRepository userRepository ;
// CREATE SELF REFRENCE
#Autowired
private ComponentService componentService;
#Override
#Transactional
public boolean validateName(String name) {
try{
retrun userRepository.validate(name);
}catch(Exception e){
componentService.handleError(name);
throw new Exception("user not valid");
}
}
#Override
#Transactional(propagation=Propagation.REQUIRES_NEW)
public boolean handleError(String name) {
userRepository.update(name);
}
}

Related

Why is a transaction full rollback?

The innerInvoke method is called in the outerInvoke method, and propagation = Propagation.REQUIRES_NEW is used in the innerInvoke method to prevent any problem in the transaction applied to the outerInvoke method even if there is a problem in the transaction applied to the innerInvoke method.
#Slf4j
#Service
#RequiredArgsConstructor
public class OuterService {
private final InnerService innerService;
private final FooRepository fooRepository;
#Transactional
public void outerInvoke() {
fooRepository.save(new Foo("foo"));
innerService.innerInvoke();
}
}
#Slf4j
#Service
#RequiredArgsConstructor
public class InnerService {
private final BarRepository barRepository;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerInvoke() {
try {
barRepository.save(new Bar("bar"));
} catch (Exception e) {
log.error("innerInvoke Exception : {}", e);
}
}
}
#SpringBootTest
class TransactionPropagationTestApplicationTests {
#Autowired
private OuterService outerService;
#Test
#Rollback(value = false)
void saveTest() {
outerService.outerInvoke();
}
}
However, if an IdentifierGenerationException, a subtype of DataAccessException, occurs in outerInvoke, all transactions are rolled back.
In my opinion, since the transactions are separated, the transaction of the outerInvoke method should be committed and the transaction of the innerInvoke method should be rolled back, so why are they all rolled back?

How to rollback transaction from service class?

I am trying to rollback for some condition by throwing exception. But i can not find proper way to do this. Here is my service class
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
And here is my BasicEM class
#Component
#Transactional(value = "transactionManager", rollbackFor = Exception.class)
public class BasicEM {
#PersistenceContext(unitName = "db1")
private EntityManager em;
public void update(Object target) {
em.merge(target);
}
}
So what i want is to call that update method, then if the condition is true undo the update.
My intention is that when i throw the exception, the transaction ends and does not commit the update. But i am wrong and the update is already done. Please help me to achieve my goal.
In order to achieve what you want you will need to have a transaction already in the Service method. The default propagation type for #Transactional(value = "transactionManager", rollbackFor = Exception.class) is Propagation.REQUIRED which means that if your Service is already included in a transaction, the basicEM.Update(entity) will also be included in such transaction.
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#Transactional(value = "transactionManager",
propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
If RollBackException is a RuntimeException you don't need to explicitly configure that the transaction should rollback when it is thrown. If it is not, then you need to configure it as follows: #Transactional(value = "transactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class).

TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'merge' call

We have some old spring applications where we have little bit spring-boot annotations. I have a scenario where I want to perform merge using EntityManager, but this is throwing "javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'merge' call" exception. I have tried solutions available in other post such as using javax.persistent #Trasactional annotations on the upload() method level, but nothing worked. These are the classes I am using -
ApplicationContextProvider.java
#Service
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ac) throws BeansException {
context = ac;
}
}
MyConfigType.java
#Entity
#Table(name = "config_loaded_table")
public class MyConfigType {
#Id
private int id;
#Column(name = "file_name")
private String fileName;
// getters and setters
}
ConfigUploader.java (abstract class)-
public abstract class ConfigUploader {
public abstract String upload() throws Exception;
}
ConfigLoader implementation class where I am using merge from entityManager-
public class MyConfigLoader extends ConfigUploader {
private int id;
private String path;
public MyConfigLoader(int id, String path) {
this.id= id;
this.path=path;
}
#Override
public String upload() throws Exception {
try {
MyConfigType myConfigType = new MyConfigType();
myConfigType.setFileName("employee.config");
// at this line I am getting exception.
int id = ApplicationContextProvider.getApplicationContext().getBean(EntityManager.class).merge(myConfigType).getId();
myConfigType.setId(id);
}catch (Exception e) {
// getting javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'merge' call
log.error(e);
}
}
}
And finally, the main class where I am calling upload() method of ConfigLoader implementation-
public class ConfigThread implements Runnable {
#Override
public void run() {
ConfigUploader configLoader = new MyConfigLoader(id,path);
configLoader.upload(); // calling upload() method here
}
}
Unfortunetly, your class is not a spring component, so #Transactional can not be used here. You can try something like this:
EntityManager entityManager = ApplicationContextProvider.getApplicationContext().getBean(EntityManager.class);
entityManager.getTransaction().begin();
entityManager.merge(...);
entityManager.getTransaction().commit();

Spring Boot Service Junit Mockito issue

I have written Junit for the service. mocking dao.
Service method return type in EmployeeDTO.
Dao return type is Employee.
problem is employee to employeeDto conversion failed in test case.
when(dao.method()).thenReturn(new Employee), so on call od service.method() I am facing issue since dozer is in between to convert employee to employeedto in the actual code.
any suggestions to fix this.
#SpringBootTest(classes = { EmployeeSearchService.class, EmployeeDao.class })
public class EmployeeSearchServiceTest {
#Mock // will create a mock implementation for the EmployeeDao
EmployeeDao employeeDao;
#InjectMocks // will inject the mocks marked with #Mock to this instance when it is created.
private EmployeeSearchService employeeSearchService ;
#Mock
private DozerBeanMapper dozerBeanMapper;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#DisplayName("fetchEmployees with valid data")
#Test
public void testfetchEmployeesWithValidData() throws IOException {
when(employeeDao.fetchEmployees()).thenReturn(Stream
.of(new Employee((long) 1, "James", "Java", "Manager", "Google"),
new Employee((long) 2, "Richard", "C++", "Manager", "Microsfot"))
.collect(Collectors.toList()));
//when(dozer.map(Mockito.any(), Mockito.any())).thenReturn(employeeDTO);
System.out.println(employeeSearchService.fetchEmployees());
assertEquals(4, employeeSearchService.fetchEmployees().size());
}
}
#Service
public class EmployeeSearchServiceImpl implements EmployeeSearchService {
#Autowired
EmployeeDao employeeDao;
#Autowired
DozerBeanMapper dozerBeanMapper;
#Override
#Logging
public List<EmployeeDTO> fetchEmployees() throws IOException {
List<Employee> aEmployeeList = employeeDao.fetchEmployees();
List<EmployeeDTO> aEmployeeDTOList= aEmployeeList.stream().map(emp ->
dozerBeanMapper.map(emp,
EmployeeDTO.class)).collect(Collectors.toList());
if (aEmployeeList.isEmpty()) {
throw new EmployeeNotfoundException("Employee Details Not Available");
}
return aEmployeeDTOList;
}
}
#Repository
public class EmployeeDaoImpl implements EmployeeDao {
#Override
#Logging
public List<Employee> fetchEmployees() throws IOException {
List<String> aFileList=fileUtils.getFileContent(EmployeeSearchConstants.EMPLOYEE_DETAILS_PATH);
List<Employee> aEmployeList = getEmployee(aFileList);
if (aEmployeList.isEmpty()) {
throw new EmployeeNotfoundException("Employee Details Not Available");
}
return aEmployeList;
}
}
If I understand you correctly your issue is that you are looking for a way to convert your Employee into a EmployeeDTO object, which in your code is done using dozerBeanMapper.map(emp, EmployeeDTO.class).
One option would be to change the EmployeeSearchServiceImpl and use Constructor Injection instead of Field Injection. This way you could simply use the real dozer class to do the mapping (by manually passing the mock for employeeDao and the real dozerBeanMapper).
Constructor Injection is done by moving the #Autowired to the constructor instead of the fields. Depening on your spring version and in case you only have one constructor for the class you might be able to omit the annotation. For more information check here.
EmployeeDao employeeDao;
DozerBeanMapper dozerBeanMapper;
#Autowired
public EmployeeSearchServiceImpl(EmployeeDao employeeDao, DozerBeanMapper dozerBeanMapper) {
this.employeeDao = employeeDao;
this.dozerBeanMapper = dozerBeanMapper;
}
Another option would be to use Mockito's thenAnser functionality. However
you still require something to do the conversion for you.
when(dozerBeanMapper.map(Mockito.any(), Mockito.any())).thenAnswer(new Answer<EmployeeDTO>() {
public EmployeeDTO answer(InvocationOnMock invocation) {
Employee employee = (Employee) invocation.getArguments()[0];
// convert to EmployeeDTO
EmployeeDTO dto = ...
return dto;
}
});

MongoTemplate null pointer exception in class

I have been looking at other answers but none of the seem to work for me, I have a spring boot application where I am using mongo and kafka. In the main class where my run() method is I am able to #Autowired mongoTemplate and it works but then in another class I did the same and I am getting a null pointer exception on the mongoTemplate.
Here are both classes:
Working
#SpringBootApplication
public class ProducerConsumerApplication implements CommandLineRunner {
public static Logger logger = LoggerFactory.getLogger(ProducerConsumerApplication.class);
public static void main(String[] args) {
SpringApplication.run(ProducerConsumerApplication.class, args).close();
}
#Autowired
private Sender sender;
#Autowired
MongoTemplate mongoTemplate;
#Override
public void run(String... strings) throws Exception {
Message msg = new Message();
msg.setCurrentNode("my_node");
msg.setStartTime(System.currentTimeMillis());
String json = "{ \"color\" : \"Orange\", \"type\" : \"BMW\" }";
ObjectMapper objectMapper = new ObjectMapper();
msg.setTest(objectMapper.readValue(json, new TypeReference<Map<String,Object>>(){}));
sender.send(msg);
mongoTemplate.createCollection("test123");
mongoTemplate.dropCollection("test123");
}
Not working
#Component
public class ParentNode extends Node{
#Autowired
public MongoTemplate mongoTemplate;
public void execute(Message message) {
try{
// GET WORKFLOWS COLLECTION
MongoCollection<Document> collection = mongoTemplate.getCollection("workflows");
} catch(Exception e){
e.printStackTrace();
}
}
}
Thank you for the help. It is much appreciated.
can you try to inject dependency with setter or constructor:
Method 1:
#Component
public class ParentNode extends Node{
#Autowired
public ParentNode(MongoTemplate mongoTemplate){
this.mongoTemplate = mongoTemplate;
}
private final MongoTemplate mongoTemplate;
public void execute(Message message) {
try{
// GET WORKFLOWS COLLECTION
MongoCollection<Document> collection = mongoTemplate.getCollection("workflows");
} catch(Exception e){
e.printStackTrace();
}
}
Method 2:
#Component
public class ParentNode extends Node{
#Autowired
public void setMongoTemplate(MongoTemplate mongoTemplate){
ParentNode.mongoTemplate = mongoTemplate;
}
static private MongoTemplate mongoTemplate;
public void execute(Message message) {
try{
// GET WORKFLOWS COLLECTION
MongoCollection<Document> collection = mongoTemplate.getCollection("workflows");
} catch(Exception e){
e.printStackTrace();
}
}

Resources