Here's a solution that worked for me:
Ivar's rambling: No single default persistence unit defined: ""
Monday, November 16, 2009
Saturday, October 24, 2009
Reusable DAOs: CRUD operations with domain objects
(belongs to this series of articles)
Trying to create reusable DAO objects, following the article "Don't repeat the DAO" for the reusable domain objects proposed earlier in this series.
It is proposed to follow the recipe from the IBM site referred to above:
And the JPA implementation of this:
The only original twist in the above is the "postFindById()" method. It is a hack for something that would be normally out of scope for this article -- the problem of lazy loading collections in an entity object that is used outside of the transaction scope. This is the reason for the widespread error: "org.hibernate.LazyInitializationException: failed to lazily initialize a collection ... no session or session was closed" that would happen in the following widespread case:
(1) display a list of objects
(2) display the details of one of them, part of those details being lazy-loading collections of dependent objects
If the object is a JPA/Hibernate entity, then the ORM session would indeed be closed between (1) and (2). There are many ways to code around this, but one that I find the most generic is to make DAO methods that retrieve the entity by ID (and by definition work with a single entity, as opposed to a list) force the loading of lazy-loading collections. Which collections need to be loaded can't be determined in the generic DAO, hence the delegating of this to subclasses using an abstract method.
Trying to create reusable DAO objects, following the article "Don't repeat the DAO" for the reusable domain objects proposed earlier in this series.
It is proposed to follow the recipe from the IBM site referred to above:
package com.example.crud.dao;
import java.io.Serializable;
import java.util.Collection;
import com.example.domain.DomainObject;
/**
* Generic DAO interface - usable for CRUD and more.
*
* Throws unchecked exceptions for any errors. The exception type depends on the
* implementation.
*
* @author maxim
*
* @param <T>
* domain object type
* @param <PK>
* primary key type
*/
public interface GenericDao<T extends DomainObject<PK>, PK extends Serializable> {
/**
* Persist the newInstance object into database
**/
void create(T newInstance);
/**
* Retrieve an object that was previously persisted to the database using
* the indicated id as primary key
*/
T findById(PK id);
/**
*
* @return all instances of T as a collection
*/
Collection<T> findAll();
/**
* Main paging method
*
* @param pageSize
* number of records (page size)
* @param firstRecord
* start from this record (inclusive)
* @param order
* field name to sort
* @param asc
* sort ascending if true, descending otherwise
*
* @return all instances that belong to the page
*/
Collection<T> getPage(int pageSize, int firstRecord, String order,
boolean asc);
/**
* Save changes made to a persistent object.
*
* @return TODO
*/
T update(T transientObject);
/**
* Remove an object from persistent storage in the database
*/
void remove(T object);
/**
* Remove an object from persistent storage in the database by ID
*/
void remove(PK id);
/**
*
* @return total number of entities in the DB
*/
Long getTotalEntities();
/**
* Overload of "getPage" with a possibility to apply dynamic criteria.
* Service layer could use this to implement find by related record, for
* example
*
* @see #getPage(int, int, String, boolean)
*
* @param pageSize
* page size
* @param firstRecord
* first record number, starts from 0
* @param order
* sort order defined by field name, e.g. "id"
* @param asc
* true for ascending sort
* @param queryString
* string to add to query, e.g. "where o.id=?", or null
* @param parameters
* objects to use the query with
* @return up to <code>pageSize</code> of objects or empty collection
*/
Collection<T> getPage(int pageSize, int firstRecord, String order,
boolean asc, String queryString, Object... parameters);
}
And the JPA implementation of this:
/**
*
*/
package com.example.crud.dao.jpa;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import javax.persistence.EntityManager;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.example.crud.dao.GenericDao;
import com.example.domain.DomainObject;
/**
* Implements {@link GenericDao} using JPA.
*
* Works in managed environment using the injected {@link EntityManager}
*
* @author maxim
*
*/
public class GenericDaoJpa<T extends DomainObject<PK>, PK extends Serializable>
implements GenericDao<T, PK> {
private static Log logger = LogFactory.getLog(GenericDaoJpa.class);
@PersistenceContext
public transient EntityManager em;
public Class<T> type;
@SuppressWarnings("unchecked")
public GenericDaoJpa() {
type = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
/*
* (non-Javadoc)
*
* @see
* com.example.prototype.persistence.GenericDao#create(java.lang.Object )
*/
public void create(T newInstance) {
em.persist(newInstance);
}
/*
* (non-Javadoc)
*
* @seecom.example.struts2.crud.persistence.GenericDao#findById(java.io.
* Serializable)
*/
public T findById(PK id) {
T entity = em.find(type, id);
postFindById(entity, em);
return entity;
}
/**
* Override in subclasses to do something here: this is where we could force
* the loading of lazy loading collections, for example.
*
* The rationale is that we typically need this for individual records, but
* not for collections; so getPage() and the like return only eager-loading
* properties, whereas findById() and the like should return fully loaded
* objects.
*
* @param entity
* the entity retrieved by find -- call methods on for lazy
* loading objects and collections to force loading
* @param em
*/
protected void postFindById(T entity, EntityManager em) {
}
/*
* (non-Javadoc)
*
* @see
* com.example.prototype.persistence.GenericDao#remove(java.lang.Object )
*/
public void remove(PK id) {
remove(em.getReference(type, id));
}
/*
* @see
* com.example.prototype.persistence.GenericDao#remove(java.lang.Object
*/
public void remove(T object) {
em.remove(object);
}
/*
* (non-Javadoc)
*
* @see
* com.example.prototype.persistence.GenericDao#update(java.lang.Object )
*/
public T update(T transientObject) {
try {
return em.merge(transientObject);
} catch (OptimisticLockException e) {
logger.error("Record is modified by another process", e);
throw new RuntimeException(
"Error saving record: modified by another process");
}
}
@Override
public Collection<T> findAll() {
Query query = em.createQuery("select o from " + type.getSimpleName()
+ " o");
return findAll(query);
}
@SuppressWarnings("unchecked")
protected Collection<T> findAll(Query q) {
return q.getResultList();
}
@Override
public Collection<T> getPage(int pageSize, int firstRecord, String order,
boolean asc) {
return getPage(pageSize, firstRecord, order, asc, null, (Object) null);
}
@SuppressWarnings("unchecked")
public Collection<T> getPage(int pageSize, int firstRecord, String order,
boolean asc, String queryString, Object... parameters) {
final String whereString = queryString == null ? "" : queryString;
final String ascending = asc ? "asc" : "desc";
final String orderString = null == order || order.trim().equals("") ? ""
: "order by " + order + " " + ascending;
final Query q = em.createQuery("select o from " + type.getSimpleName()
+ " o " + whereString + " " + orderString);
if (null != parameters && null != queryString) {
for (int i = 0; i < parameters.length; i++) {
q.setParameter(i + 1, parameters[i]);
}
}
if (0 < pageSize) {
q.setMaxResults(pageSize);
}
q.setFirstResult(firstRecord);
return q.getResultList();
}
/**
*
* @return the total number of entities
*/
public Long getTotalEntities() {
Query query = em.createQuery("select count(o) as count from "
+ type.getSimpleName() + " o");
return (Long) query.getSingleResult();
}
public EntityManager getEm() {
return em;
}
public void setEm(EntityManager em) {
this.em = em;
}
}
The only original twist in the above is the "postFindById()" method. It is a hack for something that would be normally out of scope for this article -- the problem of lazy loading collections in an entity object that is used outside of the transaction scope. This is the reason for the widespread error: "org.hibernate.LazyInitializationException: failed to lazily initialize a collection ... no session or session was closed" that would happen in the following widespread case:
(1) display a list of objects
(2) display the details of one of them, part of those details being lazy-loading collections of dependent objects
If the object is a JPA/Hibernate entity, then the ORM session would indeed be closed between (1) and (2). There are many ways to code around this, but one that I find the most generic is to make DAO methods that retrieve the entity by ID (and by definition work with a single entity, as opposed to a list) force the loading of lazy-loading collections. Which collections need to be loaded can't be determined in the generic DAO, hence the delegating of this to subclasses using an abstract method.
Monday, October 19, 2009
Spring Web Flow with Pop-Up View States
Setting up an application that uses pop-up view states with a JSF view has worked very well, except for the pop-ups; I had to spend a while on trying to make them work and had been barking up a forest of wrong trees before I finally got to downloading the "booking-jsf" sample from Spring Source Download (highly recommended, something I should have started from), and went to compare the configuration. The important points:
(1) Follow the Spring Web Flow reference for setting up Facelets and Web Flow integration
(2) The view state that uses the pop-up should be also explicitly rendering a part of the view (one doesn't need the whole in the modal window; the page itself, on the other hand, should be complete, so as to degrade gracefully and display even when pop-ups are disabled)
(if anyone noticed the "redirect", more on that later)
(3) Make sure the view that invokes the popup uses "sf:commandButton", and not normal JSF/Tomahawk/WhateverLibrary tag that one uses for buttons
(4) Finally, make sure the transition uses "redirect"; normally, this is the default, but, in my case, I have disabled it in the flow engine configuration (need to use legacy JSF views that persist state using "t:saveState"); if disabled, redirect transition can be enabled by "redirect='true'" view definition attribute in the flow definition
It is the failure to ensure (4) that was making my popup to either not display or produce obscure errors, as the flow view rendering was trying to render the popup inside the invoking screen and was missing the element with the same ID to replace with the popup content.
(1) Follow the Spring Web Flow reference for setting up Facelets and Web Flow integration
(2) The view state that uses the pop-up should be also explicitly rendering a part of the view (one doesn't need the whole in the modal window; the page itself, on the other hand, should be complete, so as to degrade gracefully and display even when pop-ups are disabled)
<view-state id="selectFiles" view="/files-select.xhtml" popup="true" redirect="true">
<on-entry>
<render fragments="content-area-popup"></render>
</on-entry>
...
</view-state>
(if anyone noticed the "redirect", more on that later)
(3) Make sure the view that invokes the popup uses "sf:commandButton", and not normal JSF/Tomahawk/WhateverLibrary tag that one uses for buttons
... <sf:commandButton value="Add Another" action="selectFiles"/> ...
(4) Finally, make sure the transition uses "redirect"; normally, this is the default, but, in my case, I have disabled it in the flow engine configuration (need to use legacy JSF views that persist state using "t:saveState"); if disabled, redirect transition can be enabled by "redirect='true'" view definition attribute in the flow definition
It is the failure to ensure (4) that was making my popup to either not display or produce obscure errors, as the flow view rendering was trying to render the popup inside the invoking screen and was missing the element with the same ID to replace with the popup content.
Subscribe to:
Posts (Atom)