I'm using Glassfish 3.1 with JSF2 and EJB stateless to query and write an Oracle DB. The table the user wants to populate in this web application has a primary key. When the user tries to add a new record the ejb method invoking em.persist is called. Now, if the user tries to add a record that has an already used primary key value, I got an exception in the EJB.
I would like to pop up a message to the user indicating that an error in the database occurred but I can't figure out how the JSF managed bean could catch the EJB exception.
Is there any way?
EJB has the concept of system exceptions and application exceptions.
Runtime exceptions, like EntityExistsException are system exceptions. These will among others cause any transaction to be rolled-ed back and cause the EJB instance bean to be discarded (destroyed). Most importantly for your problem, they will be wrapped in an EJBException.
There is no magic surrounding catching these exceptions. Adjusting the code from Petr above,
the following will just work:
Backing bean:
#EJB
private DAOBean daoBean;
public void savePerson(Entity e) {
try {
daoBean.save(e);
} catch (EJBException e) {
FacesMessage message = new FacesMessage("entity is already exists.");
FacesContext.getCurrentInstance.addMessage(null, message);
}
}
EJB:
private EntityManager em;
public void save(Entity e) {
em.persist(e);
}
Note that you can retrieve the cause of the exception to see if was an EntityExistsException or not (omitted above for brevity).
Since you probably have no need to destroy your EJB instance for this case, a better pattern is to define your own exception that inherits from a RuntimeException and is annotated with the #ApplicationException with the rollback attribute set to true.
E.g.
#ApplicationException(rollback = true)
public class MyException extends RuntimeException {
public MyException(Throwable cause) {
super(cause);
}
}
Wrap your EntityExistsException in your EJB into this exception and throw and catch it.
I strongly advise you NOT to use error codes or boolean success/failure as a result. This is a well-known anti pattern and makes your code incredible error prone.
You can create a custom exception class. Let's say UserException with a enum values of possible exception option.
In you EJB you can define your methods as throwable. If you need throw a exception.
In your JSF-SiteBean you only need to use a simple try/catch.
Is exception from type UserException ... get enum reason ... etc.
Related
I have a simple listener with 3 methods. and a repository with autowired on that. While saving an object from afterWrite it works nicely. but when saving item from onError methods no exception occurs, however it is not saving any data. Thankful for suggestions.
public class WriteListener implements ItemWriteListener{
public void beforeWrite(List items) {
System.out.println("Going to write following items: "+ items.toString());
}
public void onWriteError(Exception exception, List items) {
System.out.println("Error occurred when writing items!");
testRepository.save(items.get(0)); //not working
}
public void afterWrite(List items) {
testRepository.save(items.get(0)); //not nicely and save data
Based on the limited information provided, most likely the cause is the exception itself. The exception would have marked current transaction as dirty thus spring would have rolled it back.
If you still want to store data in your listener despite existing exception, use it in a separate transaction context. Simplest way for that would be to use #Async annotation on your listener and marking it Transactional explicitly to ensure it initiate a new transaction. Check out Spring Event which covers this topic in little bit more depth.
In the service layer, I have some method who have a transactional annotation.
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
save db problem issue.
}
}
It seem like if there are a issue, there are roll back... and nothing is saved in the db...
ProcessPaymentException extend Exception
Is there a way to rollback the process in the try but do the save in the catch?
Edit
Nested transaction could be a solution if this link is ok
https://www.credera.com/blog/technology-insights/java/common-oversights-utilizing-nested-transactions-spring/
Existing answer of using ControllerAdvise should help in normal setup that incoming requests are coming through Spring MVC (i.e. through a Controller).
For cases that is not, or you do not want to tie your exception handling logic to Spring MVC, here are some alternatives I can think of
(Here I assume you want to rely on declarative transaction control instead of programmatically controlling transactions yourself)
Separate service/component to save error in different transaction.
In short, you can have a separate service, which create its own transaction by propagation REQUIRES_NEW. e.g.
#Service
public class FooService
#Inject
private ErrorAuditService errorAuditService;
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
errorAuditService.saveErrorAudit(ppe.getErrorText());
throw ppe; // I guess you want to re-throw the exception
}
}
}
#Service
public class ErrorAuditService
#Transactional(propagation=REQUIRES_NEW)
public void saveErrorAudit() {
// save to DB
}
}
One step further, if the error handling it the same for different services, you may create an advise, which will be called when service method throws exception. In that advise, you can save the error in db (using ErrorAuditService), and rethrow the exception.
Because processes of try-catch are wrapped by the same transaction.
The transaction manager do rollback whenever an exception is thrown. So, not thing would be saved.
Is there a way to rollback the process in the try but do the save in the catch?
Yes. Create Exception Handler to save db problem issue after rollback.
this is the idea
#ControllerAdvice
public class HandlerName {
#ExceptionHandler(ProcessPaymentException.class)
public void saveDbIssue(ProcessPaymentException ex) {
// save db problem issue.
}
But it only works if u want to save static data.
My Spring application is layered as Bean, Service and DAO. All the #Transactional annotations are in service layer.
This is the pseudo code in one particular scenario.
UserBean.java
saveUser() {
userService.manageUser();
}
UserServiceImpl.java
#Transactional
public void manageUser() {
userDAO.createUser();
userDAO.updateParentUser();
}
UserDAOImpl.java
createUser() {
//insert user record in database
}
updateParentUser() {
//update parent user's database record
}
In my save user test case, the update parent user operation can fail in some cases due to primary key violation which is kind of expected.
As the #Transactional annotation is implemented in service class, this violation exception will be notified in bean class only.
What is the option to get this PK violation notification in my service class?
[Then I can handle it from there in a different business process.]
If I add a new method in service class and call manageUser() from there the #Transactional annotation will not work properly. This is due to the limitation/property of AOP. Spring expects external call to #Transactional methods.
The create/update won't be committed until you return from the #Transactional method. If the create/update is flushed to the database before that then you may get the exception within the method, but in your case it's not being flushed until the commit.
You can force the create/update to be flushed before the commit. You don't say whether you're using Hibernate or JPA, but session.flush() or entityManager.flush() should do the trick.
Use programmatic transaction management and handle exceptions in try catch block
Introduce a delegate class and do manageUser in a transaction there:
#Transactional(REQUIRED)
public void manageUser() {
try{
delegate.manageUser();
} catch (Exception ex ) {
//handle
}
}
And in delegate class
#Transactional(REQUIRES_NEW)
public void manageUser() {
}
Instead of Spring proxy based AOP I moved to AspectJ approach. This gives me the flexibility to make my manageUser() method call from another method of same service class and I can handle the exception there.
I have the following classes:
public interface GarageRepository extends PagingAndSortingRepository<Garage, Integer> {}
public class GarageBO {
private GarageRepository garageRepository;
public void updateGarage(Garage garage) {
try {
garageRepository.save(garage);
} catch (Exception e) {
throw BoozinaExceptions.getCodeException(garage, e);
}
}
}
public class GarageFacade implements GarageService {
private GarageBO garageBO;
#Transactional
public void updateGarage(Garage garage) {
garageBO.updateGarage(garage);
}
}
Supposing that i'm trying to update a garage and an unique violation is throwed.
When i call updateGarage from GarageFacade with the #Transactional annotation, garageRepository doesn't throws any exception.
When i call the same method without the #Transactional annotation, garageRepository throws the unique violation exception and now i can convert the exception using BoozinaExceptions.getCodeException(garage, e);
This behavior happens because when i have the #Transactional annotation, the exception will be throwed when Spring jpa data execute the commit. This happens after GarageBO.updateGarage execution ok ?
But i need to convert the unique violation. How can i do that ?
How to handle spring exception after commit then convert to my exception ?
What you describe is the expected behavior. The transaction is committed after the method ends, the commit leads to the unique constraint violation.
Adding #Transactional to the service methods makes the transaction end after the call to the service method the transaction is committed. Removing it makes the transaction commit after the call to the repository method.
Also why do you have a BO a Facade and a Repository?! Basically the BO and Facade are the same IMHO.
TO fix your your problem have your GarageRepository extend JpaRepository instead of PagingAndSortingRepository and call the saveAndFlush method instead of save. This will execute the sql (not committing the transaction) and trigger a constraint violation exception.
Another solution is to instead of doing a try/catch in your BO create and Aspect which does the conversion. Saves you coding the try/catch each time you need it.
I'm having the strangest thing happening and I can't figure out why. The best way to describe this is to provide a simplistic example:
#Service
#Transactional
public class Foo{
public ModelAndView delete(#ModelAttribute("abc") Long id) {
ModelAndView mav = new ModelAndView();
try {
getDaoService().delete(id); //Calls Bar.delete()
} catch (final Exception e) {
// Add a custom error message to the mav for the user to see
mav.getModelMap().addAttribute(blah, blah);
}
return mav;
}
}
#Service
#Transactional
public class Bar {
public void delete(final E entity) throws HibernateException {
if (null != entity) {
try {
sessionFactory.getCurrentSession().delete(entity);
} finally {
sessionFactory.getCurrentSession().flush();
}
}
}
}
In this particular case, I am trying to delete an object which has a constraint violation (ORA-02292). I expect the delete to fail because of this. When the delete fails, I wish to show the user an appropriate custom message.
Instead of being able to show the user a custom message, the call fails and displays the following to the screen:
org.springframework.transaction.UnexpectedRollbackException: Transaction
rolled back because it has been marked as rollback-only
When I use a debugger, I can see that the error is appropriately caught and that the ModelAndView object has the custom message inside of it. So, I have no clue why an exception is still being thrown after it has been caught and dealt with. Does anyone have insight into why this is happening?
On the #Transactional annotation, you can state whether or not to roll back your transaction due to a given exception using the noRollbackForClassName attribute. You can do it similar to this.
#Service
#Transactional(noRollbackForClassName = "java.lang.Exception")
public class YourClass {
...
}
However, note that just saying noRollbackForClassName = "java.lang.Exception" would mean it will not rollback for any Exception (or its subclasses), hence its not a good practice.
What you should do is, figure out what exception is actually thrown first (may be by printing out the e.getClass().getName()), then set that class name as the noRollbackForClassName value.
Reason wise, this is happening because if some exception is thrown while attempting to delete(), the current transaction is automatically marked as roll back only, and if it is attempted to be committed, the exception you see will be thrown. The way to get passed this is to explicitly state that this certain exception should not cause a roll back.
The issue is because once an exception is thrown, Spring internally marks the tx as rollback-only. This is completely separate from Java exception handling. You have several options:
Make sure your expected exception does not throw exceptions which extend RuntimeException; Spring only rolls back tx's when its a type RuntimeException (see this page, section 10.5.3). HibernateException extends RuntimeException, so that's why you're getting the rollback marker.
Run each tx in its own transaction by moving the transactional method to its own class and annotating it using #Transactional(propagation=Propagation.REQUIRES_NEW). Then each call will run in its own tx and will not affect the overall tx.
Use the noRollbackForClassName style venushka mentioned. But use with caution, for the reason mentioned.
The Exception is being thrown in Bar#delete and is caught in Foo#delete. There is a #Transactional annotation on Bar#delete which is crossed before the exception is caught. This inner transaction is participating in the outer transaction and so the entire transaction is marked for rollback.
To avoid this you could remove the #Transactional annotation for Bar#delete. This method is already called within the scope of the other transaction.
Add property "globalRollbackOnParticipationFailure" to the hibernateTransactionManager bean definition as follows.
<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="hibernateSessionFactory" />
**<property name="globalRollbackOnParticipationFailure" value="false" />**
</bean>