Thursday, February 21, 2013

Multiple pre and post actions with JPA using AspectJ

JPA provides you with some annotations to denotate methods (in the entities class) to be executed before / after persist / update / remove operations.
For example, @PrePersist could be used to generate a UUID for your entity (as JPA does not provide a way to fully customize the ID generator).
@PrePersist
private void ensureId(){
 setId(UUID.randomUUID().toString());
}
There is a limitation: just one method can be annotated with each of the annotation. For example, you can't have two methods marked as @PrePersist.

My solution is to create my own annotations and then write an aspect that inject in every @Entity class a method marked as @PrePersist / @PostPersist / ... that inspects the class looking for the methods marked with my own annotation and invoke them on the current object. This is really much more difficult to say than to implement... :-)

Annotations definition is quite straight forward
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PrePersistAction {

 int value() default 0;

 String description() default "";
 
 boolean alwaysBefore() default false;

}
For brevity, I only pasted the code for the pre persist actions annotations. You can notice that every anotation holds some property:

  1. value: to create an order between action. If two actions hold the same value, then the order in which they are declared will be respected.
  2. description: usefull for logging
  3. alwaysBefore: this action must be executed before non-alwaysBefore annotations (for exampe, create an UUID). If multiple actions are marked as alwaysBefore, then refer to value to know the prder in which they'll be invoked

Once we have annotations, we can go on with our aspect. Pay particular attention to the pointcut which matches all the @javax.persistence.Entity classes.
public aspect ActionSupportAspect {
 
 private static interface EntitySupport {};
 
 declare parents : @Entity * implements EntitySupport;

 private void EntitySupport.invokeActions(Class annotationType) {
  Logger log = LoggerFactory.getLogger(this.getClass());

  Comparator comparator = this.retrieveComparatorForAction(annotationType);
  if (comparator == null)
   return;

  List actions = new ArrayList(ClassUtils.getMethods(this.getClass(),
    annotationType, true));
  Collections.sort(actions, comparator);

  if (actions.isEmpty()) {
   log.trace("\tNo action to execute");
  } else {
   for (Method action : actions) {
    Annotation annotation = action.getAnnotation(annotationType);
    String description = this.retrieveDescriptionForAction(annotation);
    log.trace(String.format("Executing action @ <%s>: %s", action.toString(), description));
    action.setAccessible(true);
    try {
     action.invoke(this);
    }
    catch (Exception e) {
     log.error(
       String.format("Can't perform action: %s @ <%s>", description,
         this.getClass()), e);
    }
   }
  }
 }

 @PrePersist
 private void EntitySupport.doPrePersist() {
  Logger log = LoggerFactory.getLogger(this.getClass());
  log.trace(String.format("Executing pre persist actions on <%s>", this.toSuperString()));
  this.invokeActions(PrePersistAction.class);
  log.trace(String.format("Executed pre persist actions on <%s>", this.toSuperString()));
 }

 @PostPersist
 private void EntitySupport.doPostPersist() {
  Logger log = LoggerFactory.getLogger(this.getClass());
  log.trace(String.format("Executing post persist actions on <%s>", this.toSuperString()));
  this.invokeActions(PostPersistAction.class);
  log.trace(String.format("Executed post persist actions on <%s>", this.toSuperString()));
 }

 @PreUpdate
 private void EntitySupport.doPreUpate() {
  Logger log = LoggerFactory.getLogger(this.getClass());
  log.trace(String.format("Executing pre update actions on <%s>", this.toSuperString()));
  this.invokeActions(PreUpdateAction.class);
  log.trace(String.format("Executed pre update actions on <%s>", this.toSuperString()));
 }

 @PostUpdate
 private void EntitySupport.doPostUpdate() {
  Logger log = LoggerFactory.getLogger(this.getClass());
  log.trace(String.format("Executing post update actions on <%s>", this.toSuperString()));
  this.invokeActions(PostUpdateAction.class);
  log.trace(String.format("Executed post update actions on <%s>", this.toSuperString()));
 }

 @PreRemove
 private void EntitySupport.doPreRemove() {
  Logger log = LoggerFactory.getLogger(this.getClass());
  log.trace(String.format("Executing pre remove actions on <%s>", this.toSuperString()));
  this.invokeActions(PreRemoveAction.class);
  log.trace(String.format("Executed pre remove actions on <%s>", this.toSuperString()));
 }

 @PostRemove
 private void EntitySupport.doPostRemove() {
  Logger log = LoggerFactory.getLogger(this.getClass());
  log.trace(String.format("Executing post remove actions on <%s>", this.toSuperString()));
  this.invokeActions(PostRemoveAction.class);
  log.trace(String.format("Executed post remove actions on <%s>", this.toSuperString()));
 }

}
[Please note that this is only an excerpt of the aspect. Full code is available via SVN.]

As you can see, this aspect inject into every @javax.persistence.Entity class methods marked as @PrePersist, etc... that internally invoke @PrePersistAction, etc... methods simulating to have the @PrePersist method split in various classes.
This is it! You can check out the full code (fully featured, along with some test) here!

Hope you'll find this useful,
Stefano

Monday, February 4, 2013

Spring 3 - Version 3.2.1 and NoUniqueBeandefinitionException

For those who are migrating from spring 3.1.x.RELEASE to the latest 3.2.1.RELEASE, I would like to signal a known bug.

The solution is however quite easy: just remove the version 3.2.1.RELEASE from your build.
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-xxx</artifactId>
 <version>[3.0.0, 3.2.1.RELEASE),(3.2.1.RELEASE,4.0.0)</version>
</dependency>

This stole me a week of life... but... what do you want? That's the developer life.

Hope this can save some of your time,
Stefano

Saturday, January 19, 2013

How to get rid of lazy collection initialization in Hibernatee4

Is there anybody out there who never hit the error
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: [...], no session or session was closed?

Well, now with Hibernate4 (starting from version 4.1.6.Final) the troubles are gone, hopefully once for all!

The trick is very simple. Just locate your persistence.xml file and add

<property name="hibernate.enable_lazy_load_no_trans" value="true"/>

under the "properties" node.