Adam Bien's Weblog

Wednesday Jun 24, 2009

Generic CRUD Service aka DAO - EJB 3.1/0 Code - Only If You Really Needed

The term Data Access Object (DAO) is actually wrong. An object has state and behavior, and the behavior even can depend on the state. DAO is stateless, only only consists of data manipulation operations. Data Access Procedure (DAP) would be a more exact term. A Data Access Service, or "CRUD Service" would be a better, and even buzzword-compatible, term :-). 

javax.persistence.EntityManager is already a perfect realization of the DAO (DAP) pattern - it is directly injectable into Services (I will cover Services later) or ServiceFacades. Just for CRUD use cases - DAOs are not needed - they would degrade to plain delegates and so dead code. In real projects, however, you will get likely some reusable queries, which should be maintained in a central place. In that case it is absolutely worth to introduce a DAO. If you really need it, you could deploy one DAO-instance, for all use cases. It is easy with generics:

public interface CrudService {
    public  T create(T t);
    public   T find(Class type,Object id);
    public   T update(T t);
    public void delete(Class type,Object id);
    public List findWithNamedQuery(String queryName);
    public List findWithNamedQuery(String queryName,int resultLimit);
    public List findWithNamedQuery(String namedQueryName, Map parameters);
    public List findWithNamedQuery(String namedQueryName, Map parameters,int resultLimit);
}

 The interface-implementation is actually a Service, which always has to be executed behind a ServiceFacade:

@Stateless
@Local(CrudService.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class CrudServiceBean implements CrudService {
   
    @PersistenceContext
    EntityManager em;
    
    public  T create(T t) {
        this.em.persist(t);
        this.em.flush();
        this.em.refresh(t);
        return t;
    }

    @SuppressWarnings("unchecked")
    public  T find(Class type,Object id) {
       return (T) this.em.find(type, id);
    }

    public void delete(Class type,Object id) {
       Object ref = this.em.getReference(type, id);
       this.em.remove(ref);
    }

    public  T update(T t) {
        return (T)this.em.merge(t);
    }

    public List findWithNamedQuery(String namedQueryName){
        return this.em.createNamedQuery(namedQueryName).getResultList();
    }
    
    public List findWithNamedQuery(String namedQueryName, Map parameters){
        return findWithNamedQuery(namedQueryName, parameters, 0);
    }

    public List findWithNamedQuery(String queryName, int resultLimit) {
        return this.em.createNamedQuery(queryName).
                setMaxResults(resultLimit).
                getResultList();    
    }

    public List findByNativeQuery(String sql, Class type) {
        return this.em.createNativeQuery(sql, type).getResultList();
    }
    
   public List findWithNamedQuery(String namedQueryName, Map parameters,int resultLimit){
        Set> rawParameters = parameters.entrySet();
        Query query = this.em.createNamedQuery(namedQueryName);
        if(resultLimit > 0)
            query.setMaxResults(resultLimit);
        for (Entry entry : rawParameters) {
            query.setParameter(entry.getKey(), entry.getValue());
        }
        return query.getResultList();
    }
}


The only problem are the parameters. The number of parameters is not known in advance, so it has to be passed from the application. I used a simple Builder-Utility, which makes the parameter construction a bit more concise.

public class QueryParameter {
    
    private Map parameters = null;
    
    private QueryParameter(String name,Object value){
        this.parameters = new HashMap();
        this.parameters.put(name, value);
    }
    public static QueryParameter with(String name,Object value){
        return new QueryParameter(name, value);
    }
    public QueryParameter and(String name,Object value){
        this.parameters.put(name, value);
        return this;
    }
    public Map parameters(){
        return this.parameters;
    }
}

 The query-construction is almost fluent:


       int size = this.crudServiceBean.findWithNamedQuery(Book.BY_NAME_AND_PAGES,
                with("name",book.getName()).
                and("pages", book.getNumberOfPages()).
                parameters()).
                size();
        assertEquals(1,size);


I used the implementation above in many Glassfish v2(1) projects, but never in that puristic way. I always added project-specific queries - the actual added value. I wouldn't deploy DAOs just for CRUD, but many architects love it. Its sometimes easier to deploy some superfluous code - just to avoid more expensive discussions :-).

I pushed the origin sample project into http://kenai.com/projects/javaee-patterns - with some unit-tests and two variants. I tested the sample project with Glassfish Prelude v3 and WAR-deployment with Netbeans 6.7m1 - it should work with 6.7rc3 as well.

[The code was originally published in this Real World Java EE Patterns book, Page 141


[my tweets]  Rss My book: Real World Java EE - Rethinking Best Practices

Kommentare:

Hi Adam,

that is exactly the way, we implemented the service in our project.
Just the findByXXX we have not implemented. These are implemented in the specific way for the special services then, but I will think on adding these too.

For creating queries we use ejb3-criteria [http://code.google.com/p/ejb3criteria] It does not support all features jet, but enough for us to build queries.

Regards,

Stephan

Gesendet von Stephan am June 24, 2009 at 09:43 AM CEST #

By the way, we added also a validate-method wich is called before insert/update. Because of missing validation in JPA, we use validation of Hibernate:
<code>
/**
* Validates the given entity.
* If the validation fails an {@link ValidationException} is thrown.
* @param entity the entity to validate
*/
@SuppressWarnings("unchecked")
protected void validate(T entity) {

if (entity != null) {
ClassValidator validator = new ClassValidator(entity.getClass());
// get the invalid values
InvalidValue[] invalidValues = validator.getInvalidValues(entity);
if (invalidValues.length > 0) {
List<String> messages = new ArrayList<String>();
for (InvalidValue value : invalidValues) {
String message = value.getPropertyName() + ": " + value.getMessage();
// add an error message for each invalid value, these
// messages will be shown to the user
messages.add(message);
}

throw new ValidationException(messages);
}
}
}
</code>

Gesendet von Stephan am June 24, 2009 at 09:49 AM CEST #

Hi Adam,

You are so resourceful to say the least.
You design in this blog is almost exactly what I have used in many project (about 7 of them now) except with the finder methods. I go with something like

List<BaseEntity> findByCriteria(BaseEntityCriteria b)

where all entities extends BaseEntity and all Criteria object extends BaseEntityCriteria.
the BaseEntityCriteria object is a object that describes the entity is such a way that a consumer can load its getters and the service would generate a query string out of the loaded parameters. I built the library to take care of multiple joins and various JPQL operators and It works just fine.

Thanks

Gesendet von Michael Enudi Chuks am June 24, 2009 at 12:36 PM CEST #

@Michael,

thanks! - yes it just works. In JPA 2.0 we will get a typesafe criteria...

regards,

adam

Gesendet von Adam Bien am June 24, 2009 at 04:46 PM CEST #

Why do you call persist, flush and refresh at every create?

Gesendet von Kristof Jozsa am June 24, 2009 at 07:41 PM CEST #

@Kristof,

it's paranoia-driven :-). It could happen, that the generated PK (from e.g. DB instance) will be not updated in the entity. With persist, flush and refresh it is more likely to receive the actual @Id...

thanks for your comment!,

adam

Gesendet von Adam Bien am June 24, 2009 at 08:15 PM CEST #

Adam, I haven't seen this behaviour yet - it might depend on the specific JPA implementation used. Anyway, we're better off with tech-driven reasons ;) and this approach might introduce heavy performance problems based on the use cases (but I'm sure this aint sound like news to you either ;))

cheers,
K

ps. can you please check if your email notification system is working correctly?

Gesendet von Kristof am June 26, 2009 at 08:22 AM CEST #

Hi Adam,

First of all, thanks for share your solution.

I think some parts of your code are missing, because of the unescaped Java code, like in the declaration public <T>T create(T t); (the <T> is missing in the code of your post).

Regards,
Davi.

Gesendet von Davi am June 29, 2009 at 07:48 PM CEST #

Hi Davi,

thanks for your hint. I had another problem - in the method delete - but fixed that. The code is actually running (at least the JUnit). Check out - http://kenai.com/projects/javaee-patterns,

thanks for the nice feedback!,

adam

Gesendet von Adam Bien am June 29, 2009 at 08:27 PM CEST #

Cool stuff! Thx.

Gesendet von launsebay am August 01, 2009 at 02:54 PM CEST #

Hi Adam,

thank you for your post. A good technique, but why don't you use generics when the method returns a list, e.g. like this:

public <T> List<T> findWithNamedQuery(Class<T> type, String queryName);

Regards,
Peter

Gesendet von Peter am August 03, 2009 at 01:34 AM CEST #

@Peter,

yes and no. You will need additional parameter, but can save the cast later. So it would not be a huge benefit - but thanks for the idea.

thanks!,

adam

Gesendet von Adam Bien am August 03, 2009 at 10:17 AM CEST #

@launsebay,

if you like it, see: http://kenai.com/projects/javaee-patterns/

thanks!,

adam

Gesendet von Adam Bien am August 03, 2009 at 10:18 AM CEST #

Hi Adam,

I adopted the generic crud service in my application. Why do you use the @TransactionAttribute(TransactionAttributeType.MANDATORY)? Because my JSF-managed bean calls the crud layer directly. Do you think I should include facade between them?

regards,
launsebay

Gesendet von launsebay am August 03, 2009 at 05:05 PM CEST #

@Launsebay,

if your application will remain simple - just make it a Facade / Boundary. If you notice any duplication, just introduce a ServiceFacade, then make it MANDATORY again... I don't like empty facades...

regards,

adam

Gesendet von Adam Bien am August 03, 2009 at 08:14 PM CEST #

Hi Adam,

I ran your example using the latest version OpenEJB with the embedded OpenJPA DB.

While create (em.persist) seems to work, I experienced that the update (em.merge) does not work since the 'newName' is not found with findWithNamedQuery.

The Transaction Type is set to EXTENDED.

Any ideas?

Greetings, Detlef

Gesendet von Detlef Folger am September 02, 2009 at 11:06 PM CEST #

This is great, i implement the same idea in my projects.
I save a lot of code with this kind of generic CRUD service bean.
"One bean to rule them all"

Gesendet von jrico am November 05, 2009 at 03:24 PM CET #

Senden Sie einen Kommentar:
  • HTML Syntax: Ausgeschaltet
Interviews/About
My Recent Book
Java One 2009
CommunityOne East N.Y.C
JavaONE 2008 Interview
Search
...the last 150 posts
...the last 10 comments
greenfire.dev.java.net
Links
my.netbeans.org
Visitors
License