I'm using #Transactional in my code and I'm created a custom exception to show error messages in specific format in UI.
public class MyCustomException extends RuntimeException
When this exception is encountered I still want to rollback my transactions, same as in case when any other exception occurs.
So to make it work, I writing below code:
// service method called from rest controller
public List<String> getMyData() {
List<String> errors = new ArrayList();
try {
businessMethod();
} catch (MyCustomException e) {
log.error(e.getMessage);
errors.add(e.getMessage)
}
return errors.
}
#Transactional(rollbackFor = {MyCustomException.class, RuntimeException.class, Exception.class})
public String businessMethod() {
// Business logic to get data that can throw MyCustomException
}
My questions are:
If I'm mentioning MyCustomException.class in rollbackFor, do I need to also mention RuntimeException.class, Exception.class. Or whatever is mentioned in rollbackFor gets appended along with default exceptions for which transaction is rolled-back.
Although I'm escaping the MyCustomException from businessMethod(), but I'm catching it on its calling method getMyData(). I'm assuming that the transaction will be rolled-back in case of exception, correct?
The transaction will be rolled back on any RuntimeException, so it is not necessary to declare your own MyException.class in rollbackFor section, since your MyException extends RuntimeException. If you declare Exception.class the rollback will be performed on any Exception. But in your case you do not need rollbackFor at all.
Yes, it is correct. Your transcation starts and ends in businessMethod().
Related
I am trying to do Transactional Rollback in my methods. Intentionally i am making the insert fails to find out . But i don't see its getting rolled back . Please help what i am missing.
#Service
public class ModesService implements IModesService{
ChargeRuleDao chargeRuleDao;
public ModesService(ChargeRuleDao chargeRuleDao){
this.chargeRuleDao = chargeRuleDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRED)
public void process(ChargeRule chargeRule){
chargeRuleDao.deleteShippingChargeAttr(shippingChargeRuleID);
chargeRuleDao.deleteShippingCharge(shippingChargeRuleID);
chargeRuleDao.deleteShippingChargeDest(shippingChargeRuleID);
//Delete
chargeRuleDao.insertShipChargeFeedRule(chargeRule);
}
In DAOImpl class i have methods like below for all deletions and insertion.
#Override
public int deleteShippingChargeAttr(String test) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("ABC" "ABC", Types.VARCHAR);
return jdbcTemplate.update(DELETE_QUERY, params);
}
You may try #Transactional(rollbackFor = XYZException.class).
XYZException should be an exception which should wrap all the exceptions/exception for which you want to rollback the transaction.
Rollback occurs by default for every unchecked exception. That means you need to throw some type unchecked exception, like for example
throw new NullPointerException();
in your insertShipChargeFeedRule(chargeRule);
more about #Transactional here https://javamondays.com/spring-transactions-explained/
I have below piece of code in my project. An exception is thrown at line # 4 still my product details are saved. I am having hard time to understand why does it save product details even after throwing the exception
I am also trying to understand if the exception thrown at line #4 is a checked or unchecked exception ? If i am throwing "throw new Exception("Details don't match")" it is a Runtime exception I am assuming?
class Product{
#Transactional
addDetails(){
try{
}
catch (Exception e) {
throw new Exception("Details dont match") //Line 4
}
productDAO.save(productDetails)
addAdditionalDetails(productDetails)
}
}
class ProductDAO {
#Transactional
public void save(Product productDetails){
entitiyManager.merge(productDetails)
}
}
I am also trying to understand if the exception thrown at line #4 is a
checked or unchecked exception?
Answer: java.lang.Exception is a checked exception.
If I am throwing "throw new Exception("Details don't match")" it is a
Runtime exception I am assuming?
Answer: No, it is not a RuntimeException. RuntimeException is those which extends java.lang.RuntimeException or its subclass.
In spring by Transaction is Rollback when a Runtime exception occurs. That means any exception thrown in a transaction which extends RuntimeException or its subclass will rollback it. But in your case, you are throwing Exception which is not a type of RuntimeException.
Solution:
I will suggest creating a Custom exception which extends RuntimeExction and throws it.
class UnmatchedDetailException extends RuntimeException{
UnmatchedDetailException(String msg){
super(msg);
}
}
And then throw the UnmatchedDetailException
throw new UnmatchedDetailException("Deatils not matched");
With default spring configurations, only un-checked runtime exceptions are rolled back. In order to customize this configuration, rollbackFor is used as a property in the #Transactional annotation.
For ex,
#Transactional(rollbackFor = { MyInvalidUserException.class,
MyApplicationException.class }) public void method() throws
MyInvalidUserException, MyApplicationException {
...
... }
I have two methods are shown below.
#Transactional
public void methodA(){
logger.trace("Executing methodA");
methodB()
logger.trace("Executing methodA completed");
}
public void methodB(){
//other codes here
try{
staffDao.queryById(1) //Fetch a record from database
}catch(EmptyResultDataAccessException e){
logger.trace("Staff does not exists")
}
//other codes here
}
When there occurs an EmptyResultDataAccessException within methodB()
, the entire transaction started on methodA() is rollbacked, by below exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
I know this is the default behaviour of spring #Transactional annotation.
For my case, I need to commit the transaction even when there is an EmptyResultDataAccessException. As EmptyResultDataAccessException is a RuntimeException, I can't use the noRollBackFor attribute of #Transactional annotation.
Can anyone suggest a solution ?
I have not looked closely at your code yet but if you just need a way to not rollback the transaction for a particular exception, you can mark that in #Transactional annotation.
#Transactional(noRollbackFor = {EmptyResultDataAccessException.class})
public void methodA(){
.
.
}
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor--
Looking at the code of deleteById in SimpleJpaRepository
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
}
I thought this is just a convenience method. If I don't want the exception I just implement it the way I need it:
repo.findById(id).ifPresent(repo::delete);
Given this example code:
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Here is what I want to achieve:
Only processItem(anItem); should rollback if exception occurs inside it.
If exception occurs, myTransactionalMethod should continue, that means the for-each should end.
If exception occurs inside myTransactionalMethod but not in processItem(anItem), myTransactionalMethod should rollback completely.
Is there a solution that doesn't involve managing transactions manually (without annotations)?.
Edit: I was thinking of using #Transactional(PROPAGATION=REQUIRES_NEW), don't know if it will work within the same bean though.
This is a common misunderstanding. Spring Transactions are implemented through proxies. Proxies are a wrapper around your class. You are accessing the processItem method from the same class, i.e. you don't go through the proxy, so you don't get any transactions. I explained the mechanism in this answer some years ago.
Solution: you need two separate Spring beans if you want nested transactions, both of them must be proxied by #Transactional.
It looks like a case for NESTED transaction. NESTED transaction starts a subtransaction with in the outer transaction with savepoint, allowing it rollback to that savepoint. Since it is a nested transactions they committed at the end of outer transation.
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
// If you want to call this method directly configure your transaction use to aspectJ for transaction handling or refactor the code. Refer - [http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo][1]
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional(PROPAGATION = PROPAGATION.NESTED)
// Throw some runtime exception to rollback or some checkedException with rollbackFor attribute set in the above annotation
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Note that, I have not yet tried this below code see if that helps. You might have to tweak it, if needed. In fact I would love to give this code a try myself sometime soon.
I've created my own exception class:
public class ValidationException extends RuntimeException { ... }
I've declared it in EJB interface method:
public interface MyApi {
void save(MyDTO dto) throws ValidationException;
}
Now I've used it in the implementation:
#Stateless
#Local(MyApi.class)
public class MyService implements MyApi {
public void save(MyDTO dto) throws ValidationException {
...
throw ValidationException(errorMessages);
}
}
However, when I call that method:
#Path("/my")
#Stateless
public class MyChannel {
#Inject private MyApi myApi;
public void save(MyDTO dto) {
try{
myApi.save(dto);
} catch (ValidationException ex) {
// do sth with the exception
}
}
}
Instead of expected ValidationException, the EJBException is thrown with the following message:
0000167f BusinessExcep E CNTR0020E: EJB threw an unexpected (non-declared) exception during invocation of method "save" on bean
It surprised me, because the exception is declared in the interface and in the implementation. How else can I declare the exception, in order to be able to use it to communicate errors to the caller?
The whole mayhem happens on the WebSphere 8.5. I'm using EJB 3.0 and WebSphere libraries. The channel is JSON REST channel in the WAR module, which is wrapped in EAR module.
I believe the root of your problem lies in choosing to have your custom ValidationException extend RuntimeException. Within Java, RuntimeException or any subclass of RuntimeException does not have to be declared using a throws clause on a method signature. The intent of RuntimeException is that it is generally used in unrecoverable bug scenarios that are the result of something done incorrectly by the method caller, such as attempting to traverse beyond the end of an array (ArrayIndexOutOfBoundsException) or passing an invalid parameter (IllegalArgumentException).
Given that you would like to make your ValidationException part of the method signature and thereby require the calling client to handle the exception, I suggest the following change:
//Modify your exception so that it
//subclasses Exception (not RuntimeException):
public class ValidationException extends Exception { ... }
You will not have to modify the MyService interface, because the save method already declares that it throws the exception. But this small change will shift the way Java handles ValidationException so that when the exception is thrown, it will behave in the way you expect (and without the extraneous noise about an "undeclared" exception).
Use the #ApplicationException annotation. For example
#ApplicationException
public class ValidationException extends RuntimeException {
private static final long serialVersionUID = 7797343376699439504L;
}
You can use it with RuntimeException so you don't have to use throws declarations.