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");
}
}
}
Related
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?
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})
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
}
}
At first I code a RestController with ExceptionHandlers and everything works well. When I added an advice that wrap my #RequestMapping (with #Around) it broke my ExceptionHandler. When exception is thrown. E.g., TypeMisMatchException, All of a sudden, my controller lost its autowired beans (all of them are null).
I added a check in #PostConstruct and saw that my controller actually wires the beans.
MyRestContoller:
#RestController
#ControllerAdvice
public class MyRestController implements MyRestControllerInterface<Throwable> {
#Autowired private ApplicationContext applicationContext;
#Autowired private FirstBean firstBean;
#Autowired private SecondBean secondBean;
#PostConstruct
private void init() {
//just to print that beans are auto wired properly
//All beans are ok
}
#RequestMapping(value=“/”, method=RequestMethod.POST)
public ResponseEntity<String> postData(#SuppressWarnings("unused") #PathVariable ValidVersion version, #RequestBody DataRequest dataRequest) throws Throwable {
//stuff
return new ResponseEntity<>(newObj, HttpStatus.CREATED);
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Throwable.class)
private ErrorInfo handleBadRequest(HttpServletRequest req, Exception e) {
log.error("Error serving URL '" + req.getRequestURL()+ "': ", e);
//do something with firstBean
//but if thrown with configured advice the firstBean is null,
//and secondBean and applicationContext...
//
return new ErrorInfo(ErrorCodes.InternalServerError, req.getRequestURL().toString(), null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
My Aspect:
#Service
#Aspect
public class SomeAspect {
#Autowired private GraphiteBeanReporter graphiteBeanReporter;
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restController() {}
#Pointcut("execution(* com.sample.MyRestController.*(..))")
public void methodPointcut() {}
#Around("restController() && methodPointcut()")
public Object whatToDoAround(ProceedingJoinPoint joinPoint) throws Throwable {
String metricName = joinPoint.getSignature().getName();
log.trace("inside the aspect of method '{}', metricName);
Context time = graphiteBeanReporter.initAndStartTimerMetic(metricName);
try {
Object result = joinPoint.proceed();
return result;
} finally {
if (time != null)
time.stop();
}
}
Just to add more information
The MyRestController and SomeAspect beans are configured in MVCCOnfig.class
while other business logic beans (i.e. FirstBean, SecondBean and more) are configured in AppConfig.class
My Initializer:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
public WebAppInitializer() {
// stuff
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { SecurityConfig.class, AppConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { MVCConfig.class };
}
}
Edit
added the implementation of whatToDoAround advice. It's basically report metric ('timer') to Graphite server using autowired bean. As I mentioned, if no exception is thrown in the controller, the metric is reported as expected.
Update -
specifically, ValidVersion is an enum that represent my API valid versions. An Exception is thrown where in the url #PathVariable is set with unknown String (one that can't be converted to enum value). This exception doesn't trigger any of my advices. I even tried to use #AfterThrowing without any success.
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.