Showing posts with label JPA. Show all posts
Showing posts with label JPA. Show all posts

04 September 2016

My thoughts about Spring Transactions

I've some thoughts about spring (and spring-boot) transactions I want to list somewhere, and I think here's a nice place.

Preface

Spring do transaction management through PlatformTransactionManager interface.

This interface has implementation for different kinds of datasources, for example for DataSourceTransactionManagerHibernateTransactionManagerHibernateTransactionManagerJmsTransactionManagerJpaTransactionManager and others.

In a typical JPA application, the JpaTransactionManager is being used.

In SpringBoot, All the JpaRepository are annotated with @Transactional annotation.

This annotation used by spring to do the Declarative transaction management.

So, the Developer uses this annotation to control many things, included the transaction propagation (REQUIRED, REQUIRED_NEW, SUPPORTED, etc), the isolation level (READ_COMMITED, READ_UNCOMMITED, etc.) and the rollback exception, timeout and more.

And behind the since, the platform-specific transaction manager along with the databasource do the actual work.

@Transactionl

So, many people says, use the @Transactional annotation on the service layer, you might need to use it on DAO (now called Repository) layer, but do not use it on the controller layer.

In spring boot, as I said, all the out-of-the-box methods that you get by implementing the JpaRepository are already have the @Transactional annotation (see SimpleJpaRepository). And I think hence the @Transactional on the implementation class itself, then all your repo methods might have the annotation as well.

But this is not the problem, the problem in the Service layer.

@Transactionl in the Service layer

What if I didn't put the Transactional annotation on my service methods?
Then your method will not run in a transaction, so If you have a service method that do the following

public void serviceMethod(){
     insert1();
     insert2();
}

insert1() might succeed and inserts data into the database, while insert2() fails.

In this case you should put the @Transactional annotation on your service method if you want to have all or none.

@Transactionl and noRollbackFor

The rollback is controlled by RuleBasedTransactionAttribute
see http://docs.spring.io/autorepo/docs/spring/current/spring-framework-reference/html/transaction.html#transaction-declarative-rolling-back

In its default configuration, the Spring Framework’s transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException. ( Errors will also - by default - result in a rollback). Checked exceptions that are thrown from a transactional method do not result in rollback in the default configuration.
Also,
When the Spring Framework’s transaction infrastructure catches an exception and is consults configured rollback rules to determine whether to mark the transaction for rollback, the strongest matching rule wins 

For rollbackFor and noRollbackFor;

I think spring take the exception thrown and check to see how it is close (in inheritance hierarchy) to the declared exceptions (in rollbackFor and noRollbackFor). If it is closer to an exception declared in rollbackFor more than one declared in noRollbackFor, then the transaction will be rollbacked. and vice versa.

ex:
We have this heirarcy:
class Exception1{}
class Exception2 extends 1{}
class Exception3 extends 2{}

@Transactional(rollbackFor=Exception1.class, noRollbackFor=Exception2 .class)
public void myMethod(){
        throw new Exception3 ();
}

In this case, the transaction will NOT be rollbacked, because the thrown exception (Exception3) is closest to exception in noRollbackFor (Exception2) than the exception in rollbackFor (Exception1)

And if the closest exception declared in rollbackFor, the spring will rollback the tx, otherwise it will not rollback it:

in RuleBasedTransactionAttribute: return !(winner instanceof NoRollbackRuleAttribute);

@Transactionl and lazy evaluation

lazy evaluation is important aspect in web development, it facilities things, lazy evaluation includes not only evaluate lazy associations in the Controller layer, but also evaluation lazy Streams. (Thanks to OpenEntityManagerInViewFilter).

But if you choose to annotate your service method with @Transactional, lazy transaction data will not be available in the Controller layer (and exceptions will be thrown as well).

Here's an example issue.

So, some people says, at service layer get all the data you want and then send it to the controller layer as eager so you don't need the service layer's transaction anymore.

I think for finder services, you might not need to have the @Transactional annotation, so you can make use of the OpenEntityManagerInViewFilter (which is provided by springboot by default) and be able to  enjoy your laziness in the controller layer.(It seems the "Open EntityManager in View" has nothing to do here, it only allow the transaction manager to use an EntityManager created by him. because if you have @Transactional and have this pattern on, the exception will continue to throw if you try to access lazy data out of the transactions.

But in case of services that updates the database, you do need to use the @Transactional annotation to control the things not to go messy!

@Transactionl on controller and service layer

If you have a finder method that returns a lazy object (java 8 stream or lazy association as in hibernate), the if you have the @Transactional annotation on the service layer, then the lazy parts of  object will not be available to the controller layer.

However, if you put the @Transactionl annotation on the controller layer as well (which many people don't recommend) then the lazy part will be available to the controller layer unless you make the transaction propagation on the service layer as REQUIRED_NEW, in this case the service layer will suspend the controller's transaction and start/end it's own transaction and will send back the object to the controller out of its transaction and the problem will still exists.

Conclusion

For now, I'll  - mostly, because some times I do not need it - use @Transactionl on Service methods that do update (Add/update/delete) the database.

However for finder service methods (that query the database), it is better to have the @Transational(readOnly=true) on it, but since I didn't understand - till now- much more, I'll choose not to use this annotation so I can deal with my laziness objects in the controller layer)

Interesting classes to see the source code of:
1. TransactionInterceptor: class source code to see what is happening internally. (very interesting)
2. TransactionAspectSupport:
  a. method: invokeWithinTransaction
  b. method: completeTransactionAfterThrowing (it shows
3. AbstractPlateformTransactoinManager
 a. method: commit (it shows the rollback if done if "setRollbackOnly" method is being called

The post above is based on my thoughts and is might not be valid.

16 July 2012

Using Dynamic Proxies to Manage Transactions to DB (3)

This is the third post and the more mature one in the same topic, Please see :

Post 1: http://m-hewedy.blogspot.com/2010/04/using-dynamic-proxies-to-centralize-jpa.html
Post 2: http://m-hewedy.blogspot.com/2012/06/using-dynamic-proxies-to-centralize.html

The following code is the more mature (but still have a bug see: http://stackoverflow.com/questions/11565022/setting-instance-variables-of-the-proxied-objects-in-the-invocationhandler#comment15299502_11565022 )


please see http://stackoverflow.com/questions/11506159/is-there-a-limit-for-maximum-number-of-concurrent-transactions-in-hibernate

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;


/**
 * http://stackoverflow.com/questions/2587702
 * 
 * @author mohammad_abdullah
 */
public class ServiceProxy implements InvocationHandler {

 private final Object object;
 private final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
 private static final String SESSION_FACTORY_FIELD = "sessionFactory";

 public static final Map ACTIVE_TRANSACTIONS = new HashMap();

 private ServiceProxy(final Object object) {
  this.object = object;
 }

 public static Object newInstance(final Object object) {
  return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new ServiceProxy(object));
 }

 @Override
 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {

  Object result = null;
  SessionFactory sessionFactory = null;
  boolean isActive = false;
  try {
   if (Modifier.isPublic(method.getModifiers())) {

    sessionFactory = HibernateUtil.getSessionFactory();

    final Field sessionField = this.object.getClass().getSuperclass().getDeclaredField(SESSION_FACTORY_FIELD);
    if (sessionField == null) {
     throw new UPSAdminException("Service Implementation should have field named: \"" + SESSION_FACTORY_FIELD + "\".");
    }
    sessionField.setAccessible(true);
    sessionField.set(this.object, sessionFactory);

    isActive = sessionFactory.getCurrentSession().getTransaction().isActive();

    if (!isActive) {
     this.logger.info("Tnx begin." + " {Method: " + method.getName() + "} {Thread: " + this.threadId() + "}");
     final Transaction newTnx = sessionFactory.getCurrentSession().beginTransaction();
     ACTIVE_TRANSACTIONS.put(this.threadId(), newTnx);
    } else {
     this.logger.info("Joing Active tnx" + " {Method: " + method.getName() + "} {Thread: " + this.threadId() + "}");
    }
    result = method.invoke(this.object, args);

    if (!isActive) {
     ACTIVE_TRANSACTIONS.remove(this.threadId()).commit();
     this.logger.info("Tnx commit" + " {Method: " + method.getName() + "} {Thread: " + this.threadId() + "}");
    }
   } else {
    result = method.invoke(this.object, args);
   }

   return result;

  } catch (final InvocationTargetException _ex) {
   final Throwable cause = _ex.getCause();
   this.logger.severe("Caller Exept: " + cause + " {Method: " + method.getName() + "} {Thread: " + this.threadId() + "}");

   if (!isActive && (sessionFactory.getCurrentSession() != null) && sessionFactory.getCurrentSession().getTransaction().isActive()) {
    ACTIVE_TRANSACTIONS.remove(this.threadId()).rollback();
   }

   if (cause instanceof HibernateException) {
    this.logger.severe("Hibernate Error. Rollback. {Method: " + method.getName() + "} {Thread: " + this.threadId() + "}");
    throw new DBException(cause.getCause().getMessage());

   } else if (cause instanceof SetRollbackException) {
    this.logger.severe("Tnx marked rollback. {Method: " + method.getName() + "} {Thread: " + this.threadId() + "}");
    return result;

   } else {
    this.logger.severe("Error in Business Method : " + method + ". Rollbacked Back." + " Thread: " + this.threadId() + "}");
    throw cause;
   }
  } catch (final Exception ex) {
   this.logger.severe("Error in Proxy code :" + ex + " {Mehtod :" + method + "} {Thread: " + this.threadId() + "}");

   if (!isActive && (sessionFactory.getCurrentSession() != null) && sessionFactory.getCurrentSession().getTransaction().isActive()) {
    ACTIVE_TRANSACTIONS.remove(this.threadId()).rollback();
   }

   if (ex instanceof HibernateException) {
    throw new DBException(ex.getCause().getMessage());
   }

   throw ex;
  }
 }

 private long threadId() {
  return Thread.currentThread().getId();
 }
}

28 June 2012

Using Dynamic Proxies to centralize Transaction Management (2)

I've talked about Using Dynamic Proxies to centralize JPA code In an earlier time, and here's a refined version of the Proxy class:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.Session;

import org.daz.DBException;
import org.daz.SetRollbackException;
import org.daz.HibernateUtil;

/**
 * http://stackoverflow.com/questions/2587702
 * 
 * @author mohammad_abdullah
 */
public class ServiceProxy implements InvocationHandler {

    private Object object;
    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
    private static final String SESSION_FIELD = "session";

    private ServiceProxy(Object object) {
        this.object = object;
    }

    public static Object newInstance(Object object) {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new ServiceProxy(object));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = null;
        Session session = null;
        boolean joined = false;
        try {
            if (Modifier.isPublic(method.getModifiers())) {

                session = HibernateUtil.getSessionFactory().getCurrentSession();

                Field sessionField = object.getClass().getSuperclass().getDeclaredField(SESSION_FIELD);
                if (sessionField == null)
                    throw new RuntimeException("Service Implementation should have field named: \"" + SESSION_FIELD + "\".");
                sessionField.setAccessible(true);
                sessionField.set(object, session);

                if (session.getTransaction().isActive()) {
                    joined = true;
                    logger.info("Using Already Active transaction");
                } else {
                    logger.info("Transaction Began");
                    session.beginTransaction();
                }
                result = method.invoke(object, args);

                if (!joined) {
                    session.getTransaction().commit();
                    logger.info("Transaction Commited");
                }
            } else {
                result = method.invoke(object, args);
            }

            return result;

        } catch (InvocationTargetException _ex) {
            Throwable cause = _ex.getCause();
            logger.severe("Caller Exception: " + cause);

            if (!joined && session != null && session.getTransaction().isActive())
                session.getTransaction().rollback();

            if (cause instanceof HibernateException) {
                logger.severe("Hibernate Error. Rollbacked Back.");
                throw new DBException(cause.getCause().getMessage());

            /*SetRollbackException is a user-defined exception that can be thrown from Business method to mark transaction for rollback */
            } else if (cause instanceof SetRollbackException) {        
                logger.severe("Transaction marked for Rollback. Rollbacked Back.");
                return result;

            } else {
                logger.severe("Error in Business Method : " + method + ". Rollbacked Back.");
                throw cause;
            }
        } catch (Exception ex) {
            logger.severe("Error in Proxy code :" + ex);

            if (!joined && session != null && session.getTransaction().isActive())
                session.getTransaction().rollback();

            if (ex instanceof HibernateException)
                throw new DBException(ex.getCause().getMessage());

            throw ex;
        }
    }
}

16 January 2012

The names of the primary key fields must correspond

I've faced some JPA Error (deployment-time error) which is:

The names of the primary key fields or properties in the primary key class [class FooBarPK] and those of the entity bean class [class FooBar] must correspond and their types must be the same. Also, ensure that you have specified id elements for the corresponding attributes in XML and/or an @Id on the corresponding fields or properties of the entity class.

The error appears when deploying some JPA project in Oracle App Server.

I've this entity:


@Entity
@IdClass(FooBarPK.class)
public class FooBar implements Serializable {
    
    private static final long serialVersionUID = 1L;
    

    @Id
    private Long id1;
    @Id
    private Long id2;
    
    //...
}

//And this is the PK class 

public class NewParaPK implements Serializable 
{
    private static final long serialVersionUID = 1L;

    private Long id1;
    private Long id2;
}

I wonder, why this happen...

I found this line:
private static final long serialVersionUID = 1L;

In the PK Class who cases the problem!

05 June 2011

Project "Dali" a very exiting JPA plugin for Eclipse

Salam,

http://www.eclipse.org/webtools/dali/

a very existing tool exist in Eclipse ( I think >= 3.5)


to open it, Window>Show Views> JPA Details

10 December 2010

Handling 2 one-to-many relationships in JPQL

Hi,

I had the following scenario:

I've Entry Department that is has one-to-many relationship with Employee and also had a one-to-many relationship with (say) Section.
And I need to Join these three entities, I've used the following query :
select d from Department d join d.employees e join d.sections s

And the generated SQL was correct.

23 November 2010

Workaround to find generated SQL on OAS

Al salmo Alykom,

I don't know if this post will be valuable or not, but let me tell you about it.

I am using Toplink as a JPA implementation on OAS from within Eclipse IDE, I am try to show the generated SQL by putting the following property in persistence.xml:

<property name="toplink.logging.level" value="FINE" />


It is supposed to work, but don't for me.

So, I find a workaround to find the generated SQL.
You will find the generated SQL here: "app_serv_home\j2ee\home\log\oc4j\log.xml"

Beware, the file is huge, so try to use a utility to tail the file for you (ex use GNU tail)

02 June 2010

A note about Cascade.MERGE

Al salamo Alykom,

Hi folks, I've a long time since I've been posted about Java EE.
So, today we gonna talking about JPA, we are talking about the merge operation.

From Specs:

The semantics of the merge operation applied to an entity X are as follows:
....
- If X is a managed entity, it is ignored by the merge operation, however, the merge operation is cascaded to entities referenced by relationships from X if these relationships have been annotated with the cascade element value cascade=MERGE or cascade=ALL annotation.


This is what I need to talk with you about:

Here' an example:
Consider we have a uni-directional many-to-one relationship between Jobs and the user that do that job:


public class User{
// .. user data here...
}

public class Job{

private User user;
//....

public User getUser(){
return user;
}
}



as it appears, It is a uni-directional from Job to User (many-to-one)

suppose we have the following annotation over the getUser operation:
@ManyToOne(cascade=CascadeType.MERGE)

this means, when a managed Job have changed its User, on merging the Job Object to DB, also merge the in-relation Object (User).


Here's a code example that prove this specs element:

Job job = em.find(Job.class, 1);
User u = job.getUser();
u.setName("mohammed");
em.merge(job);


After this code, the field name of User will be updated to be "mohammed".

06 April 2010

Using Dynamic Proxies to centralize JPA code

I was working on some project that required me not to use an Application Server, hence I've chosen JPA to persist data, I had to use JPA in standalone application (actually it is a web application that intended to run in tomcat)

One solution is to use some lightweight container such as Spring instead of real Containers, but this project was so simple that i needn't to complex the Dev process (and introduce many jars ....)

I went with using Java Daynaic Proxy to centralize the code/control of the JPA; here's the code of the proxy:

package com.forat.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import com.forat.service.exceptions.DAOException;

/**
 * Example of usage :
 * <pre>
 * OnlineFromService onfromService = 
 *            (OnlineFromService) DAOProxy.newInstance(new OnlineFormServiceImpl());
 *        try {
 *            Student s = new Student();
 *            s.setName("Mohammed");
 *            s.setNationalNumber("123456");
 *            onfromService.addStudent(s);    
 *        }catch (Exception ex) {
 *            System.out.println(ex.getMessage());
 *        }
 *</pre>
 * @author mohammed hewedy
 *
 */
public class DAOProxy implements InvocationHandler{

    private Object object;
    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());

    private DAOProxy(Object object) {
        this.object = object;
    }
    
    public static Object newInstance(Object object) {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), 
                    object.getClass().getInterfaces(), new DAOProxy(object));
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        EntityManagerFactory emf = null;
        EntityManager em = null;
        EntityTransaction et = null;
        Object result = null;
        try {
            emf = Persistence.createEntityManagerFactory(Constants.UNIT_NAME);
            em = emf.createEntityManager();;
            Method entityManagerSetter = object.getClass().
                getDeclaredMethod(Constants.ENTITY_MANAGER_SETTER_METHOD, EntityManager.class);
            entityManagerSetter.invoke(object, em);
            et = em.getTransaction();
            et.begin();
            result = method.invoke(object, args);
            et.commit();
            return result;
        }catch (InvocationTargetException ex) {  // Exception in Called code
            // Any exception that occur in the invoked method body can be got by ex.getCause();
// you can rollback transaction here 
        }catch (Exception ex) {// Exception in Proxy code
            et.rollback();
            Throwable cause = ex.getCause();
            logger.log(Level.SEVERE, cause.getMessage());
            if (cause instanceof DAOException)
                throw new DAOException(cause.getMessage(), cause);
            else
                throw new RuntimeException(cause.getMessage(), cause);
        }finally {
            em.close();
            emf.close();
        }
    }
}

Here's the service Interface "OnlineFromService":
package com.forat.service;

import java.util.Set;

import com.forat.model.Student;
import com.forat.service.exceptions.StudentNotFoundException;

public interface OnlineFromService {
    
    public void fillFaculties(String[] facultiesNames);
    public void addFaculty(String facultyName);
    
    public void fillGovernrates(String[] GovernratesNames);
    public void addGovernrate(String GovernrateName);
    
    public void addStudent(Student student);
    public Student findStudent(String nationalNumber) throws StudentNotFoundException;
    public void updateStudent(Student student);
    
    public Set<Student> getAllStudents();
}

And here's the Service Implementation:
import java.util.Set;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;

import com.forat.model.Student;
import com.forat.service.exceptions.StudentNotFoundException;

/**
 * This class should be called through {@link DAOProxy} as it injects the {@link EntityManager} and other JPA staff
 * and takes care of the transactions (commiting/rollbacking) , etc...
 * @see DAOProxy
 * @author mohammed
 */
public class OnlineFormServiceImpl implements OnlineFromService {

    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
    
    private EntityManager em;
    
    public OnlineFormServiceImpl() {
    }
    
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }
    // the code has been minimized, the code that is responsalbe of working with JPA staff has moved to the proxy class
    // we we do here just use the EntityManager (as if we were in a managed environment)
    public void addStudent(Student student) {
        em.persist(student);
    }

    // other interface methods goes here ...

}

The idea is to centralize the control of the persistence code, and this implementation is a basic implementation that may contains errors.
Also, this code my includes performance impact, as I used reflections to invoke service methods, but for sure its performance impact will be more less than of application servers!