When I firstly tried EJB 3.0 in my applications I found that I need to write a log of EntityManager-like classes. They are very similar to DAO objects and each of they holds all possible operations for his entity. My app was full of similar lines of code. So I found some solutions on the Internet and combined them with my own thoughts. The following class is a result of several tries to implement the best solution:

 

 

package ccgproj.backend.rdbms.em;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import ccgproj.backend.exceptions.CreateException;
import ccgproj.backend.exceptions.DataStoreException;
import ccgproj.backend.exceptions.FinderException;
import ccgproj.backend.exceptions.RemoveException;
import ccgproj.backend.exceptions.UpdateException;
import ccgproj.backend.rdbms.entity.IEntity;
/**
* Class type: base class for entityManager EJB's<br/>
* <br/>
* Orders:<br/>
* 1) provides base methods for managing entity <br/>
* 2) provides protected methods for executing queries <br/>
*
* @author sorokoumov
*/
@TransactionManagement(TransactionManagementType.CONTAINER)
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public abstract class GenericManager<T extends IEntity> {
    /**
     * injected JPA entity manager
     */
    @PersistenceContext(unitName = "CCGProjectPersistenceUnit")
    private EntityManager em;
    /**
     * creates entity
     *
     * @param obj
     * @return
     * @throws CreateException
     */
    public T create(T obj) throws CreateException {
        try {
            em.persist(obj);
            return obj;
        } catch (Exception e) {
            throw new CreateException(e);
        }
    }
    /**
     * updates entity
     *
     * @param obj
     * @return
     * @throws UpdateException
     */
    public T update(T obj) throws UpdateException {
        try {
            obj = em.merge(obj);
            return obj;
        } catch (Exception e) {
            throw new UpdateException(e);
        }
    }
    /**
     * refreshes entity(this action completely resets a state of entity)
     * @param obj
     * @throws DataStoreException
     */
    public void refresh(T obj) throws DataStoreException {
        em.refresh(obj);
    }
    /**
     * removes entity but it refreshes it before.
     * Refreshing is required because entity could be detached/or composed out of transaction.
     * @param obj
     * @throws RemoveException
     */
    public void remove(T obj) throws RemoveException {
        try {
            em.refresh(obj);
            em.remove(obj);
        } catch (Exception e) {
            throw new RemoveException(e);
        }
    }
    /**
     * checks if entityManager contains this entity
     * @param obj
     * @return
     * @throws DataStoreException
     */
    public boolean contains(T obj) throws DataStoreException {
        try {
            return em.contains(obj);
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * detaches entity.
     * It's very useful when you need to apply some changes to entity within a transaction
     * but you don't want the container to call merge method because you want to perform a custom update to this entity.
     * @param obj
     * @throws DataStoreException
     */
    public void detach(T obj) throws DataStoreException {
        try {
            if (em.contains(obj)) {
                em.detach(obj);
            }
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * returns class of T
     * @return
     */
    @SuppressWarnings("unchecked")
    private Class<T> getEntityClass() {
        ParameterizedType ptype = (ParameterizedType) getClass().getGenericSuperclass();
        return (Class<T>) ptype.getActualTypeArguments()[0];
    }
    /**
     * finds entity ny PK
     * @param pk
     * @return
     * @throws FinderException
     */
    public T findByPrimaryKey(Object pk) throws FinderException {
        T obj = null;
        try {
            obj = em.find(getEntityClass(), pk);
        } catch (Exception e) {
            throw new FinderException(e);
        }
        if (obj == null) {
            throw new FinderException(getEntityClass().getName() + " with id "
                    + pk + " not found");
        }
        return obj;
    }
    /**
     * executes named query with a single result. Hence if result is not found or
     * it contains multiply entries method throws FinderExpection.
     * @param namedQuery
     * @param params
     * @return
     * @throws FinderException
     * @throws DataStoreException
     */
    protected Object executeQuerySingleResult(String namedQuery,
            Object... params) throws FinderException, DataStoreException {
        try {
            Query query = em.createNamedQuery(namedQuery);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    query.setParameter(i + 1, params[i]);
                }
            }
            List<?> list = query.getResultList();
            if (list.size() != 1 || list.get(0) == null) {
                throw new FinderException("Multiply or no entries.");
            }
            return list.get(0);
        } catch (FinderException e) {
            throw e;
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * executes named query with multiply results
     * @param namedQuery
     * @param params
     * @return
     * @throws DataStoreException
     */
    protected List<?> executeQueryMultiplyResult(String namedQuery,
            Object... params) throws DataStoreException {
        try {
            Query query = em.createNamedQuery(namedQuery);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    query.setParameter(i + 1, params[i]);
                }
            }
            return query.getResultList();
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * executes named query with multiply results.
     * This method has startPosition and maxResult parameters.
     * @param namedQuery
     * @param startPosition
     * @param maxResult
     * @param params
     * @return
     * @throws DataStoreException
     */
    protected List<?> executeQueryMultiplyResult(String namedQuery,
            Integer startPosition, Integer maxResult, Object... params)
            throws DataStoreException {
        try {
            Query query = em.createNamedQuery(namedQuery);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    query.setParameter(i + 1, params[i]);
                }
            }
            if (startPosition != null) {
                query.setFirstResult(startPosition);
            }
            if (maxResult != null) {
                query.setMaxResults(maxResult);
            }
            List<?> list = query.getResultList();
            return list;
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * finds list of entities with named query. So that it helps not cast results to List<T> in other classes.
     * @param namedQuery
     * @param params
     * @return
     * @throws DataStoreException
     */
    @SuppressWarnings("unchecked")
    protected List<T> findObjectsList(String namedQuery, Object... params)
            throws DataStoreException {
        return (List<T>) executeQueryMultiplyResult(namedQuery, params);
    }
    /**
     * finds list of entities with named query. So that it helps not cast results to List<T> in other classes.
     * This method has startPosition and maxResult parameters.
     * @param namedQuery
     * @param startPosition
     * @param maxResult
     * @param params
     * @return
     * @throws DataStoreException
     */
    @SuppressWarnings("unchecked")
    protected List<T> findObjectsList(String namedQuery, Integer startPosition,
            Integer maxResult, Object... params) throws DataStoreException {
        return (List<T>) executeQueryMultiplyResult(namedQuery, startPosition,
                maxResult, params);
    }
    /**
     * finds a single object
     * @param namedQuery
     * @param params
     * @return
     * @throws FinderException
     * @throws DataStoreException
     */
    protected Object findSingleObject(String namedQuery, Object... params)
            throws FinderException, DataStoreException {
        try {
            return executeQuerySingleResult(namedQuery, params);
        } catch (FinderException e) {
            throw e;
        } catch (DataStoreException e) {
            throw e;
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * finds a single entity
     * @param namedQuery
     * @param params
     * @return
     * @throws FinderException
     * @throws DataStoreException
     */
    @SuppressWarnings("unchecked")
    protected T findSingleEntityObject(String namedQuery, Object... params)
            throws FinderException, DataStoreException {
        return (T) findSingleObject(namedQuery, params);
    }
    /**
     * executes named query with no result. like update/delete
     * @param namedQuery
     * @param params
     * @throws DataStoreException
     */
    protected void executeNoResultQuery(String namedQuery, Object... params)
            throws DataStoreException {
        try {
            Query query = em.createNamedQuery(namedQuery);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    query.setParameter(i + 1, params[i]);
                }
            }
            query.executeUpdate();
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * executes EJB-QL/JPA-QL query and returns a list of objects
     * @param ejbQl
     * @param startPosition
     * @param maxResult
     * @param params
     * @return
     * @throws DataStoreException
     */
    protected List<?> executeEJBQLQueryMultiplyResult(String ejbQl,
            Integer startPosition, Integer maxResult, Object... params)
            throws DataStoreException {
        try {
            Query query = em.createQuery(ejbQl);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    query.setParameter(i + 1, params[i]);
                }
            }
            if (startPosition != null) {
                query.setFirstResult(startPosition);
            }
            if (maxResult != null) {
                query.setMaxResults(maxResult);
            }
            List<?> list = query.getResultList();
            return list;
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    /**
     * executes EJB-QL/JPA-QL query and returns a single object
     * @param ejbQl
     * @param params
     * @return
     * @throws FinderException
     * @throws DataStoreException
     */
    protected Object executeEJBQLQuerySingleResult(String ejbQl,
            Object... params) throws FinderException, DataStoreException {
        try {
            Query query = em.createQuery(ejbQl);
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    query.setParameter(i + 1, params[i]);
                }
            }
            List<?> list = query.getResultList();
            if (list.size() != 1 || list.get(0) == null) {
                throw new FinderException("Multiply or no entries.");
            }
            return list.get(0);
        } catch (Exception e) {
            throw new DataStoreException(e);
        }
    }
    public static interface ILoadMethod<T> {
        public List<T> find() throws DataStoreException;
    }
    /**
     * this method loads entries from DB using loader and updates all found entries, all entries which are not found would be deleted.<br/>
     * for comparing entities this method uses equals so don't forget to override it
     * @param elements
     * @param loader
     * @return result of operation
     */
    public List<T> executeSmartListUpdate(List<T> elements, ILoadMethod<T> loader) throws DataStoreException {
        List<T> result = new ArrayList<T>();
        List<T> loaded = loader.find();
        List<T> forDelete = new ArrayList<T>();
        for (T element : loaded) {
            if (!elements.contains(element)) {
                forDelete.add(element);
            }
        }
        for (T element : forDelete) {
            remove(element);
        }
        for (T element : elements) {
            if (loaded.contains(element)) {
                update(element);
            } else {
                create(element);
            }
        }
        return result;
    }
}

/**

* Class type: base class for entityManager EJB's<br/>

* <br/>

* Orders:<br/>

* 1) provides base methods for managing entity <br/>

* 2) provides protected methods for executing queries <br/>

*

* @author sorokoumov

*/

@TransactionManagement(TransactionManagementType.CONTAINER)

@TransactionAttribute(TransactionAttributeType.REQUIRED)

public abstract class GenericManager<T extends IEntity> {

 

    /**

     * injected JPA entity manager

     */

    @PersistenceContext(unitName = "unitName")

    private EntityManager em;

 

    /**

     * creates entity

     *

     * @param obj

     * @return

     * @throws CreateException

     */

    public T create(T obj) throws CreateException {

        try {

            em.persist(obj);

            return obj;

        } catch (Exception e) {

            throw new CreateException(e);

        }

    }

 

    /**

     * updates entity

     *

     * @param obj

     * @return

     * @throws UpdateException

     */

    public T update(T obj) throws UpdateException {

        try {

            obj = em.merge(obj);

            return obj;

        } catch (Exception e) {

            throw new UpdateException(e);

        }

    }

 

    /**

     * refreshes entity(this action completely resets a state of entity)

     * @param obj

     * @throws DataStoreException

     */

    public void refresh(T obj) throws DataStoreException {

        em.refresh(obj);

    }

 

    /**

     * removes entity but it refreshes it before.

     * Refreshing is required because entity could be detached/or composed out of transaction.

     * @param obj

     * @throws RemoveException

     */

    public void remove(T obj) throws RemoveException {

        try {

            em.refresh(obj);

            em.remove(obj);

        } catch (Exception e) {

            throw new RemoveException(e);

        }

    }

 

    /**

     * checks if entityManager contains this entity

     * @param obj

     * @return

     * @throws DataStoreException

     */

    public boolean contains(T obj) throws DataStoreException {

        try {

            return em.contains(obj);

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * detaches entity.

     * It's very useful when you need to apply some changes to entity within a transaction

     * but you don't want the container to call merge method because you want to perform a custom update to this entity.

     * @param obj

     * @throws DataStoreException

     */

    public void detach(T obj) throws DataStoreException {

        try {

            if (em.contains(obj)) {

                em.detach(obj);

            }

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * returns class of T

     * @return

     */

    @SuppressWarnings("unchecked")

    private Class<T> getEntityClass() {

        ParameterizedType ptype = (ParameterizedType) getClass().getGenericSuperclass();

        return (Class<T>) ptype.getActualTypeArguments()[0];

    }

 

    /**

     * finds entity ny PK

     * @param pk

     * @return

     * @throws FinderException

     */

    public T findByPrimaryKey(Object pk) throws FinderException {

        T obj = null;

        try {

            obj = em.find(getEntityClass(), pk);

        } catch (Exception e) {

            throw new FinderException(e);

        }

        if (obj == null) {

            throw new FinderException(getEntityClass().getName() + " with id "

                    + pk + " not found");

        }

        return obj;

    }

 

    /**

     * executes named query with a single result. Hence if result is not found or

     * it contains multiply entries method throws FinderExpection.

     * @param namedQuery

     * @param params

     * @return

     * @throws FinderException

     * @throws DataStoreException

     */

    protected Object executeQuerySingleResult(String namedQuery,

            Object... params) throws FinderException, DataStoreException {

        try {

            Query query = em.createNamedQuery(namedQuery);

            if (params != null) {

                for (int i = 0; i < params.length; i++) {

                    query.setParameter(i + 1, params[i]);

                }

            }

            List<?> list = query.getResultList();

            if (list.size() != 1 || list.get(0) == null) {

                throw new FinderException("Multiply or no entries.");

            }

            return list.get(0);

        } catch (FinderException e) {

            throw e;

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * executes named query with multiply results

     * @param namedQuery

     * @param params

     * @return

     * @throws DataStoreException

     */

    protected List<?> executeQueryMultiplyResult(String namedQuery,

            Object... params) throws DataStoreException {

        try {

            Query query = em.createNamedQuery(namedQuery);

            if (params != null) {

                for (int i = 0; i < params.length; i++) {

                    query.setParameter(i + 1, params[i]);

                }

            }

            return query.getResultList();

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * executes named query with multiply results.

     * This method has startPosition and maxResult parameters.

     * @param namedQuery

     * @param startPosition

     * @param maxResult

     * @param params

     * @return

     * @throws DataStoreException

     */

    protected List<?> executeQueryMultiplyResult(String namedQuery,

            Integer startPosition, Integer maxResult, Object... params)

            throws DataStoreException {

        try {

            Query query = em.createNamedQuery(namedQuery);

            if (params != null) {

                for (int i = 0; i < params.length; i++) {

                    query.setParameter(i + 1, params[i]);

                }

            }

            if (startPosition != null) {

                query.setFirstResult(startPosition);

            }

            if (maxResult != null) {

                query.setMaxResults(maxResult);

            }

            List<?> list = query.getResultList();

            return list;

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * finds list of entities with named query. So that it helps not cast results to List<T> in other classes.

     * @param namedQuery

     * @param params

     * @return

     * @throws DataStoreException

     */

    @SuppressWarnings("unchecked")

    protected List<T> findObjectsList(String namedQuery, Object... params)

            throws DataStoreException {

        return (List<T>) executeQueryMultiplyResult(namedQuery, params);

    }

 

    /**

     * finds list of entities with named query. So that it helps not cast results to List<T> in other classes.

     * This method has startPosition and maxResult parameters.

     * @param namedQuery

     * @param startPosition

     * @param maxResult

     * @param params

     * @return

     * @throws DataStoreException

     */

    @SuppressWarnings("unchecked")

    protected List<T> findObjectsList(String namedQuery, Integer startPosition,

            Integer maxResult, Object... params) throws DataStoreException {

        return (List<T>) executeQueryMultiplyResult(namedQuery, startPosition,

                maxResult, params);

    }

 

    /**

     * finds a single object

     * @param namedQuery

     * @param params

     * @return

     * @throws FinderException

     * @throws DataStoreException

     */

    protected Object findSingleObject(String namedQuery, Object... params)

            throws FinderException, DataStoreException {

        try {

            return executeQuerySingleResult(namedQuery, params);

        } catch (FinderException e) {

            throw e;

        } catch (DataStoreException e) {

            throw e;

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * finds a single entity

     * @param namedQuery

     * @param params

     * @return

     * @throws FinderException

     * @throws DataStoreException

     */

    @SuppressWarnings("unchecked")

    protected T findSingleEntityObject(String namedQuery, Object... params)

            throws FinderException, DataStoreException {

        return (T) findSingleObject(namedQuery, params);

    }

 

    /**

     * executes named query with no result. like update/delete

     * @param namedQuery

     * @param params

     * @throws DataStoreException

     */

    protected void executeNoResultQuery(String namedQuery, Object... params)

            throws DataStoreException {

        try {

            Query query = em.createNamedQuery(namedQuery);

            if (params != null) {

                for (int i = 0; i < params.length; i++) {

                    query.setParameter(i + 1, params[i]);

                }

            }

            query.executeUpdate();

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * executes EJB-QL/JPA-QL query and returns a list of objects

     * @param ejbQl

     * @param startPosition

     * @param maxResult

     * @param params

     * @return

     * @throws DataStoreException

     */

    protected List<?> executeEJBQLQueryMultiplyResult(String ejbQl,

            Integer startPosition, Integer maxResult, Object... params)

            throws DataStoreException {

        try {

            Query query = em.createQuery(ejbQl);

            if (params != null) {

                for (int i = 0; i < params.length; i++) {

                    query.setParameter(i + 1, params[i]);

                }

            }

            if (startPosition != null) {

                query.setFirstResult(startPosition);

            }

            if (maxResult != null) {

                query.setMaxResults(maxResult);

            }

            List<?> list = query.getResultList();

            return list;

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

    /**

     * executes EJB-QL/JPA-QL query and returns a single object

     * @param ejbQl

     * @param params

     * @return

     * @throws FinderException

     * @throws DataStoreException

     */

    protected Object executeEJBQLQuerySingleResult(String ejbQl,

            Object... params) throws FinderException, DataStoreException {

        try {

            Query query = em.createQuery(ejbQl);

            if (params != null) {

                for (int i = 0; i < params.length; i++) {

                    query.setParameter(i + 1, params[i]);

                }

            }

            List<?> list = query.getResultList();

            if (list.size() != 1 || list.get(0) == null) {

                throw new FinderException("Multiply or no entries.");

            }

            return list.get(0);

        } catch (Exception e) {

            throw new DataStoreException(e);

        }

    }

 

}

 

Some notes about GenericManager:
1) I created several exceptions which extend Exception class. I guess that every application should have its own set of exceptions.
2) I suppose that every method which finds something in DB and returns single result should throw FinderException instead of returning null.
3) Object... params is a very helpful way to pass unlimited count of parameters.
4) startPosition and maxResult could be null. In this case they are simply ignored by GenericManager.
5) some public methods like update/create etc. are automatically available in all childes as public methods. Thus you don't need to write them for every manager separately.
Simple usage sample on EJB 3.1(@LocalBean):

 

@Stateless
@LocalBean
public class UserManager extends GenericManager<User> {
    public List<User> findAllUsers(Integer start, Integer max)
            throws DataStoreException {
        return findObjectsList("findAllUsers", start, max);
    }
    public User findUserByLogin(String login) throws FinderException,
            DataStoreException {
        return findSingleEntityObject("findUserByLogin", login);
    }
}
I will appreciate any comments/suggestions about this article.

 

 

Thanks for your attention.

Filter Blog

By date:
By tag: