Mostrando entradas con la etiqueta annotations. Mostrar todas las entradas
Mostrando entradas con la etiqueta annotations. Mostrar todas las entradas

5 jun 2008

Howto: Setup Spring for Hibernate Annotations

Today, we'll explore how to setup the Spring ApplicationContext to use annotation-driven Hibernate mappings.
Hibernate mappings should be specified in any of the following:

  • Hibernate XML mapping files

  • XDoclet

  • Hibernate Annotations

Hibernate Annotations is my preferred way to map my entity classes, since they don't require any external file (thus keeping mapping info in your Java files), is fully integrated with all Hibernate mapping capabilities and Hibernate documentation encourages us to use this kind of configuration because it's more efficient.
Annotation driven mapping in Hibernate uses the standard JPA API annotations and introduce some specific extensions to deal with some Hibernate features. You can find a full reference in the official documentation.
The Spring (version 2.5) applicationContext.xml file used to configure an annotation-driven SessionFactory should look like this:
...
  <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.AnnotationSessionFactoryBean">
    <property name="dataSource">
      <ref bean="dataSource" />
    </property>
    <property name="annotatedClasses">
      <util:list>
        <value>foo.bar.model.MyEntity</value>
        <value>foo.bar.model.AnotherEntity</value>
        <value>...</value>
      </util:list>
    </property>
    <property name="hibernateProperties">
      <util:properties location="hibernate.properties Location">
    </property>
  </bean>
...

Unfortunately, Spring's AnnotationSessionFactoryBean doesn't accept wildcards in the annotatedClasses property (since it's implemented as Class[] instead of Resource[]) and the annotatedPackages property is not intended to specify the package containing our annotated entities but to 'add package level annotations at the class leve' (I don't really understand this ;-) ).
But don't worry, we can extend AnnotationSessionFactoryBean to achieve what we want like this:
public class ExtendedAnnotationSessionFactoryBean extends
 AnnotationSessionFactoryBean {

  private String[] basePackages;
  private ClassLoader beanClassLoader;

  public void afterPropertiesSet() throws Exception {
    Collection<Class<?>> entities = new ArrayList<Class<?>>();
    ClassPathScanningCandidateComponentProvider scanner = this.createScanner();
    for (String basePackage : this.basePackages) {
      this.findEntities(scanner, entities, basePackage);
    }
    this.setAnnotatedClasses(entities.toArray(new Class<?>[entities.size()]));
    super.afterPropertiesSet();
  }

  private ClassPathScanningCandidateComponentProvider createScanner() {
    ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
    scanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
    return scanner;
  }

  private void findEntities(ClassPathScanningCandidateComponentProvider scanner,
                            Collection<Class<?>> entities, String basePackage) {
    Set<BeanDefinition> annotatedClasses = scanner.findCandidateComponents(basePackage);
    for (BeanDefinition bd : annotatedClasses) {
      String className = bd.getBeanClassName();
      Class<?> type = ClassUtils.resolveClassName(className, this.beanClassLoader);
      entities.add(type);
    }
  }
  
  public void setBasePackage(String basePackage) {
    this.basePackages = new String[] { basePackage };
  }

  public void setBasePackages(String[] basePackages) {
    this.basePackages = basePackages;
  }

  public void setBeanClassLoader(ClassLoader beanClassLoader) {
    this.beanClassLoader = beanClassLoader;
  }

}

We don't need to inject the beanClassLoader property if we don't want to, thus using the default ClassLoader. This happens because we're using ClassUtils.resolveClassName() to get the Class objects, and the Javadoc on this method stands that "the class loader to use may be null, which indicates the default ClassLoader". With that in mind, we can use the following applicationContext setup:
...
  <bean id="sessionFactory"
        class="foo.bar.MyAnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="basePackages">
      <util:list>
        <value>foo.bar.model</value>
      </util:list>
    </property>
    <property name="hibernateProperties">
      <util:properties location="hibernate.properties Location">
    </property>
  </bean>
...

That's all, with that, Hibernate will inspect and map all the classes in package foo.bar.model having the required annotations (@Entiy or @MappedSuperclass).

28 may 2008

Reflection & Annotations

This post presents a simple (yet powerful) way to combine reflection and annotations to obtain all the methods present in a class' hierarchy marked with a given annotation.

package bar.foo.utils;

public class ReflectionUtils {

  /**
  * Enforce non-instatiability
  */
  private ReflectionUtils {}

  /**
   * Get all methods annotated with the specified annotation
   * searching on the leaf class and all superclasses.
   */
  public static Method[] getAllAnnotatedMethods(
      Class targetClass, Class annotationClass) {
    List list = new ArrayList();

    // traverse inheritance hierarchy
    do {
      Method[] methods = targetClass.getDeclaredMethods();
        for (Method method : methods) {
          if (method.isAnnotationPresent(annotationClass))
     methods.add(method);
        }
        targetClass = targetClass.getSuperclass();
    } while (targetClass != null);

    return (Method[]) list.toArray(new Method[list.size()]);
  }

}


It could be very useful in a variety of scenarios, e.g. auditing: by just annotating the getters in the audited entity, we'll be able to keep track of the values/changes with a few lines of code:

public class Entity extends BaseEntity {
  private Integer id;
  private MyProperty myProperty;
  
  // we don't want to audit id!
  public Integer getId() {return id;}

  // but we do want to audit myProperty
  @Auditable
  public MyProperty getMyProperty {return myProperty;}
}

...

public class AuditManager {
  public void auditEntity(BaseEntity myEntity) {
    Class entityClass = myEntity.getClass();
    Class annotationClass = Auditable.getClass();
    Method[] auditGetters = ReflectionUtils.getAllAnnotatedMethods(
                                      entityClass, annotationClass);
    
    for (Method method : auditGetters) {
      // audit logic here
    }
  }