I have a #RequestScoped, #Transactional CDI bean injected in my REST interface:
#RequestScoped
#Transactional
public class myRestCall
{
#Inject
EntityHandlerService ehs; // contains #PersistenceContext
try
{
// execute business logic, access DB via ehs (JPA / Hibernate)
}
catch(Throwable t)
{
// log exception
// -> rollback transaction
}
}
Now I like to have a try / catch around the call of the business logic that I can log the exception properly. But I need to rollback the transaction manually, unless I throw the exception again what I do not like. So how can I force the transasction rollback here? I know how to do it, if it would be a EJB: We could do
#Resource
private SessionContext ctx;
and then
ctx.setRollbackOnly();
in the catch close.
However, it is not an EJB and I cannot make an EJB out of it due to resource limits.
In case anyone is still looking for an answer, you can add the #Resource TransactionSynchronizationRegistry and then call setRollbackOnly();
#RequestScoped
class MyBean {
#Resource
private TransactionSynchronizationRegistry reg;
#Transactional
void yourmethod()
{
try {
// do stuff
} catch (Exception e) {
reg.setRollbackOnly();
}
}
}
If you can't use #Resource, you can lookup the trx resgistry with jndi:
java:comp/TransactionSynchronizationRegistry
You can include a parameter rollbackOn="Exception.class" asociated to the #Transactional annotation.
But if yo do so, you must quit the "try-catch" block
Related
I have a stateless bean that observes an event and saves a record:
#Stateless
public class Manager {
#Inject
Repository repository;
Manager() {}
#Inject
Manager(Repository repository) {
this.repository = repository;
}
public void EventHandler(#Observes MyEvent myEvent) {
save(event.obj);
}
private save(Object object) {
repository.add(object);
}
}
My repository is like this:
#Stateless
public class Repository {
#PersistenceContext
EntityManager em;
Repository() {}
public void add(Object object) {
em.persist(object);
// em.flush(); <---
}
}
It doesn’t work unless I uncomment the line
The thing I don’t understand is why do I need to flush! Shouldn’t the transaction commit automatically?
Could it be that I have an EJB container and the cdi transaction started by Observes never actually ends but do something weird? Or it ends but doesn’t commit because he doesn’t know about EJB?
persist() is not meant to insert the row immediately, but rather at flush time, which is usually immediately before transaction commit.
However, if this is not what you are asking, but the row is not inserted even after the transaction has ended, it may be that flush mode for that transaction is set to manual.
The problem is this, I have three EJB, the EJB_A have connection to the server database 1, the EJB_B have connection to the server database 2 and the third EJB_C which is responsible for calling the first two.
When there is an error in the EJB_A or EJB_B only rollback is done in the EJB where there is error, on the other EJB saved properly done, eventhough it should propagate the rollback to the two EJB.
EJB_C:
#Stateless
#LocalBean
#TransactionManagement( TransactionManagementType.BEAN )
public class EJB_C {
private EJB_A ejbA;
private EJB_B ejbB;
#PostConstruct
public void init(){
Context context = null;
try {
context = new InitialContext();
this.ayudanteSessionBean = (AyudanteSessionBean) context.lookup("java:global/Trans1/Trans1DBA/EJB_A");
this.ayudanteSessionBean = (AyudanteSessionBean) context.lookup("java:global/Trans1/Trans1DBA/EJB_B");
} catch (NamingException ex) {
Logger.getLogger(OrquestadorSessionBean.class.getName()).log(Level.SEVERE, null, ex);
}
}
public void saveTwoEJBs(EntityA, a, EntityB b){
try{
ejbA.save(a);
ejbB.save(b);
}catch (Exception ex) {
}
}
}
EJB_A:
#Stateless
#LocalBean
public class EJB_A {
#PersistenceContext( unitName = "persistA" )
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void guardarAmigo(EntityA a){
em.persist(a);
}
}
EJB_B:
#Stateless
#LocalBean
public class EJB_B {
#PersistenceContext( unitName = "persistB" )
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void guardarAmigo(EntityB b){
em.persist(b);
}
}
Could you help me?
Well, to make it work you will need to setup distributed transactions (XA) properly:
Check that all datasources are XA-ready. Typically this means different jdbc driver class name and different type of connection pool during setup.
The process is vendor-specified, but always described in documentation of your application server.
You need to remove annotation:
#TransactionManagement( TransactionManagementType.BEAN )
to let your container (actually his transaction broker) to manage transactions instead of bean.
3. You need to add #TransactionAttribute annotation on method saveTwoEJBs :
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void saveTwoEJBs(EntityA, a, EntityB b)
It's not required but there is no reason to lookup EJB beans via JNDI context, just inject them via #EJB annotation:
#EJB
private EJB_A ejbA;
#EJB
private EJB_B ejbB;
Some links to go:
http://docs.oracle.com/cd/E13222_01/wls/docs100/ejb30/examples.html
http://docs.oracle.com/cd/E16439_01/doc.1013/e13981/servtran001.htm
http://entjavastuff.blogspot.ru/2011/02/ejb-transaction-management-going-deeper.html
Just out of curiosity, is it possible to directly control an EJB transaction from the Web Container?
To illustrate I made this simple example initiating a UserTransaction in the Web Container(using a Servlet), but the transaction is not bound to the EJB Container (in this case a BMT SFSB).
Why is it? Is there a way to do it?
Stateful Session Bean using BMT
#Stateful
#TransactionManagement(TransactionManagementType.BEAN)
public class CustomerBean implements CustomerBeanLocal{
#PersistenceContext(type=PersistenceContextType.EXTENDED)
private EntityManager em;
#Override
public Integer createCustomer(String name) {
Customer customer = new Customer();
customer.setId(1);
customer.setName(name);
em.persist(customer);
//em.flush();
return customer.getId();
}
}
UserTransaction is initiated in the Servlet, but the Session Bean doesn't persist
The Customer is not persisted to the database.
public class BMTServlet extends HttpServlet {
#EJB
private CustomerBeanLocal customerBean;
#Resource
private UserTransaction userTransaction;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
userTransaction.begin();
customerBean.createCustomer("Tars");
userTransaction.commit();
} catch (Exception e) {
throw new ServletException(e);
}
}
}
If we uncomment the em.flush(); then we get the following exception:
javax.persistence.TransactionRequiredException: no transaction is in progress
org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:792)
org.jboss.ejb3.jpa.integration.JPA1EntityManagerDelegator.flush(JPA1EntityManagerDelegator.java:86)
bean.CustomerBean.createCustomer(CustomerBean.java:25)
BMT will not work in your scenario, as BMT bean will be handling transaction by itself and will not participate in the transaction started in the web module (the container transaction). To control transaction from servlet using UserTransaction, the bean must be CMT.
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.
I use Bean-managed transaction inside #ManagedBean class to manually manage transactions,
#ManagedBean(name = "clients")
#ViewScoped
#URLMapping(id = "sousc", pattern = "/ccf_sicavs_customers", viewId = "/customers/PgSouscripteurs.jsf")
#TransactionManagement(TransactionManagementType.BEAN)
public class SouscripteursBean extends Referentiel implements Serializable {
#Resource
private UserTransaction ut;
#EJB
private CustomerDaoLocal ejbCust;
public void createCustomer() {
try {
LOGGER.info("Debut de la transaction");
ut.begin();
LOGGER.info("begin() succeded");
currentMorale.setCodeSouscripteur("25");
saved=ejbCust.createCustomer(currentMorale);
ut.commit();
LOGGER.info("commit() succeded");
} catch (Exception e) {
saved=false;
try {
ut.rollback();
LOGGER.info("rollback() succeded");
} catch (Exception ex) {
LOGGER.log(Level.SEVERE,null,ex);
}
}
}
}
but if an exception is thrown thrown before the commit(), the rollback() failed
and throw this java.lang.IllegalStateException:
SEVERE: java.lang.IllegalStateException: Transaction is not active in the current thread.
at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.validateTransactionManager(JavaEETransactionManagerJTSDelegate.java:447)
at com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate.rollbackDistributedTransaction(JavaEETransactionManagerJTSDelegate.java:208)
at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.rollback(JavaEETransactionManagerSimplified.java:899)
at com.sun.enterprise.transaction.UserTransactionImpl.rollback(UserTransactionImpl.java:234)
I think you have that exception because the transaction is outside the EJB Container scope.
JSF application is the EJB service client and the application is in WEB Container scope.
Normally you don't do any business logic in JSF managed bean. Managed beans are just controllers in the JSF ModelViewController architecture. The business logic should be done inside ejb's and inject them in JSF managed beans and only call the EJB methods with the functionality. Transactions, persistence, security etc should be done inside EJB.
Bottom line, the transaction should be done inside createCustomer() method from that EJB, not in managed bean.
Try that!