How to separate two transactions calling each other in Spring - java

I have a call from transaction A to transaction B and transaction B fails, how to rollback only in transaction B but not in transaction A
#Autowired
ResourceServiceImpl resourceService;
// transaction A
#Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = RuntimeException.class)
void syncRestApi() {
for (RoleResourceRelation roleResourceRelation : roleResourceRelations) {
roleResourceRelationMapper.save(roleResourceRelation);
}
//call transaction B
try {
resourceService.test();
} catch (Exception e) {
}
}
#Service
#Transactional
public class ResourceServiceImpl {
//transaction B
public void test(){
throw new RuntimeException("");
}
}
thank you

You are throwing a RuntimeException in B that will cause the transaction of A rollback.
So you have to catch this Exception in A or you have to configure RuntimeException to not rollback (noRollbackFor), but this will not be a good idea because this may lead to problems.

Related

Spring transaction not rolled back after an exception is thrown

I have a service with a method, which is not annotated with #Transactional:
#Service
#RequiredArgsConstructor
public class MainService {
private final ServiceA serviceA;
public void processData() {
List<EntityA> list = serviceA.getList();
list.forEach(item -> {
try {
serviceA.doSomeDbOperations(item);
} catch(Exception e) {
// some processing here
} finally {
// some processing and DB interactions here
}
})
}
}
The goal is to roll back the changes happened in try block (serviceA.doSomeDbOperations(item)) if an exception is thrown. so I annotated this method in ServiceA with #Transactional:
#Service
public class ServiceA {
// dependencies
#Transactional
public void doSomeDbOperations(EntityA item) {
// some logic here
repositoryA.save(item)
serviceB.deleteSomething(input)
}
}
serviceB.deleteSomething(input) could throw an exception:
#Service
public class ServiceB {
// dependencies
public void deleteSomething(EntityA item) {
// some logic here
if(condition) {
Throw new RuntimeException();
}
}
}
The problem is that when an Exception is thrown, the changes in try block are not rolled back and data is non-consistent. Any idea where the problem is?
I guess in ServiceB you are using a checked Exception.
In Spring the transactions are rolled back just on unchecked exceptions by default, so try throwing for example a RuntimeException in your ServiceB.deleteSomething() method.
In case you need a checked Exception there, you can do something like this in your ServiceA.doSomeDbOperations() method:
#Transactional(rollbackFor = Throwable.class)
Some references:
https://www.baeldung.com/java-checked-unchecked-exceptions
How to make Spring #Transactional roll back on all uncaught exceptions?

#Transactional does not work if exception is thrown

This is my spring boot application:
#SpringBootApplication
#EnableTransactionManagement(proxyTargetClass=true)
public class WhizhopApplication {
public static void main(String[] args) {
SpringApplication.run(WhizhopApplication.class, args);
}
}
This is my service:
#Service
public class OrderService {
#Autowired
BillRepository billRepo;
#Transactional(rollbackFor = Exception.class)
public BaseDTO saveKot(BillDTO billDto) {
try {
//business logic
billRepo.save();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}
If any exception occurs, the transaction is not rolled back.
You shouldn't catch exception if you want it to be processed with #Transactional.
Also please note that:
#Transactional works only for public methods
Spring’s #Transactional does not rollback on checked exceptions
Extract business logic into separate method annotated by #Transactional(rollbackFor = Exception.class) and remove annotation from saveKot.
public BaseDTO saveKot(BillDTO billDto) {
try {
businessLogic(billDto);
} catch (Exception e) {
// catch if it needed but manually rollback is not needed
return null;
}
}
#Transactional(rollbackFor = Exception.class)
BaseDTO businessLogic(BillDTO billDto) throws Exception {
// It will be automatically rolled back if Exception occurs
...
}
By default rollback from #Transactional is happen only for Runtime execption, if you want to change this behavior you can add rollBackFor parameter to annotation
#Transactional(rollbackFor={MyException.class})

How to catch transaction exceptions in #Async?

When writing transactional methods with #Async, it's not possible to catch the #Transactional exceptions. Like ObjectOptimisticLockingFailureException, because they are thrown outside of the method itself during eg transaction commit.
Example:
public class UpdateService {
#Autowired
private CrudRepository<MyEntity> dao;
//throws eg ObjectOptimisticLockingFailureException.class, cannot be caught
#Async
#Transactional
public void updateEntity {
MyEntity entity = dao.findOne(..);
entity.setField(..);
}
}
I know I can catch #Async exceptions in general as follows:
#Component
public class MyHandler extends AsyncConfigurerSupport {
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
//handle
};
}
}
But I'd prefer to handle the given exception in a different way ONLY if it occurs within the UpdateService.
Question: how can I catch it inside the UpdateService?
Is the only chance: creating an additional #Service that wraps the UpdateService and has a try-catch block? Or could I do better?
You could try self-injecting your bean which should work with Spring 4.3. While self-injecting is generally not a good idea, this may be one of the use-cases which are legitimate.
#Autowired
private UpdateService self;
#Transactional
public void updateEntity() {
MyEntity entity = dao.findOne(..);
entity.setField(..);
}
#Async
public void updateEntityAsync(){
try {
self.updateEntity();
} catch (Exception e) {
// handle exception
}
}

Catching EJBTransactionRolledbackException

I have problem with understanding the EJBTransactionRolledbackException.
I have entity:
#Entity
public class MyEntity {
#Id
#GeneratedValue
private Long id;
#Size(max=5)
private String name;
//...
}
and repository which is SLSB because of ease of CMT:
#Stateless
public class ExampleRepository {
#PersistenceContext
private EntityManager em;
public void add(MyEntity me) {
em.persist(me);
}
}
now I have test Servlet, when I simulate ConstraintViolation (too long name).
#WebServlet("/example")
public class ExampleServlet extends HttpServlet {
#Inject
private ExampleRepository repo;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
MyEntity me = new MyEntity();
me.setName("TooLongName");
try {
repo.add(me);
} catch(EJBTransactionRolledbackException e) {
System.out.println("Exception caught");
}
}
}
I know that in such scenario EJB container will wrap the ConstraintViolationException so I catch the EJBTransactionRolledbackException instead. The problem is that in the console I can see my message from catch block ("Exception caught") but before that there are tons of exception logs produced (link).
I don't quite understand what happened - is this exception caught or not? How to prevent all these error messages in the console in such simple scenario?
Please look at this explanation: A clear explanation of system exception vs application exception
You have to understand that handling exceptions and handling transactions are two different things happening alongside. System exceptions unconditionally trigger transaction rollback. When you see a ConstraintViolationException, which is a system exception because it extends RuntimeException, it is not just wrapping and re-throwing. A bad thing have happened along the way - your transaction has been aborted.
So, answering the first question if the exception (ConstraintViolationException) was caught - yes, it was caught by the container. The transaction was aborted and a new exception was thrown to notify the application code about that.
You can suppress logging these messages, but then you would not know about data persistence failures.
I can suggest you two solutions:
Use bean managed transactions:
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
public class ExampleRepository {
#PersistenceContext(synchronization = SynchronizationType.UNSYNCHRONIZED))
private EntityManager em;
#Resource
private UserTransaction tx;
public void add(MyEntity me) {
try {
tx.begin();
em.joinTransaction();
em.persist(me);
tx.commit();
} catch (ValidationException ex) {
throw new AppValidationException(ex);
}
}
}
Delegate / facade pattern:
You leave your ExampleRepository as is:
#Stateless
public class ExampleRepository {
#PersistenceContext
private EntityManager em;
public void add(MyEntity me) {
em.persist(me);
}
}
Create new EJB without transaction (with same methods as initial):
#Stateless
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class ExampleRepositoryDelegate {
#EJB
private ExampleRepository repository;
public void add(MyEntity me) {
try {
repository.add(me);
} catch (ValidationException e) {
e.printStackTrace();
}
}
}
And in servlet you use new delegate bean:
#WebServlet("/example")
public class ExampleServlet extends HttpServlet {
#Inject
private ExampleRepositoryDelegate repoDelegate;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
MyEntity me = new MyEntity();
me.setName("TooLongName");
try {
repoDelegate.add(me);
} catch(Exception e) {
System.out.println("Exception caught");
}
}
}

How is exception thrown from Hibernate DAO to Spring MVC controller?

Iam new to spring.How can I throw the exception from my Hibernate DAO calss to service and then to my controller and how to inform UI about the exception.Plese help me with exception throwing to indicate the user.
Service Class:
#Service
public class ServiceImpl implements Service {
private static Logger LOGGER = Logger.getLogger(AccessServiceImpl.class);
#Autowired
private DAO DAO;
#Override
#Transactional
public List<PrinterVO> getDetails() {
List<VO> printerList = null;
try {
LOGGER.info("Inside getAllPrinters()");
printerList = DAO.getAllPrinters();
} catch(Exception e) {
LOGGER.error("getAllPrinters() Error :", e);
}
return printerList;
}
DAO:
public class DAOImpl implements DAO {
#Autowired
#Qualifier(value="ressessionFactory")
private SessionFactory sessionFactory;
#SuppressWarnings("unchecked")
#Override
public List<VO> getDetails() {
List<VO> printerList = null;
try {
LOGGER.info("Inside getAllPrinters()");
printerList = sessionFactory.getCurrentSession().createQuery("from PrinterVO").list();
} catch(HibernateException e) {
LOGGER.error("getAllPrinters() Error : ", e);
}
return printerList;
}
You're catching the exception, so it cannot propagate to your UI. You should only catch an exception if you have some action that you need to perform. Logging is not an action.
In your DAO, you should inject your Session (if you really want to depend on Hibernate) or a JPA EntityManager. If you are using dependency injection, you shouldn't be invoking factory methods.
I don't see anything in your code that would tell me how data gets to your UI, so I can't tell you how to pass an exception along to your UI either. Spring come with its own exception handling and translation facilities, so you shouldn't have to write many try/catch blocks, but rather rely on Spring's capabilities to present sensible error messages.

Categories