Wednesday, April 19, 2006

Spring Framework, Hibernate and EJB3 Annotations

written by Marcel Panse

One of the best new feature of Hibernate3 must be the annotation support. No more writing hbm.xml files or no more using XDoclet to make things easier. The new annotations feature in JDK5 makes it possible to define your tables and columns in your java domain objects similar to XDoclet. The big difference is that XDoclet generates hbm.xml files from your XDoclet mappings in your comments and hibernate uses the generated hbm.xml files. This way you won't have any compile time code checking or code assist. The annotation feature makes code checking and completion possible.

There isn't much to find on the internet about configuring The Spring Framework with hibernate3 using annotations. So i'm writing an example of how to do it. In the example im using JDK5.0, Spring2.0 M3 and Hibernate 3.1.

The first thing to do is to make sure you have the appropiate necessary jar files in your project:
To use Spring with Hibernate:
  • spring-2.0.jar (+some dependencies like asm.jar)
  • spring-hibernate3.jar
To Use hibernate3
  • hibernate-3.1.jar
  • cglib-2.1.3.jar (runtime dependency)
  • ehcache-1.1.jar (runtime dependency)
To use annotations
  • hibernate-annotations.jar
  • hibernate-entitymanager.jar
  • ejb3-persistence.jar

Now we'll grab a simple POJO bean of a domain class and add annotations to the POJO:
import javax.persistence.*;
@Entity
@Table(name = "Product")
public class ProductImpl implements Product
{
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;

private String description;
private Double price;
private String name;
private String creationDate;
private String comments;

//followed by the getters and setters.
}
I choose to use the 'field' style annotation instead of the property type one. Hibernate automaticly detects which style you use by just looking where you placed the @Id tag. (either at a field or at a getter-method). Pretty easy huh? Hibernate automaticly maps the properties with your database. You simple add an annotation with the settings your like (i.e. if you like the use another column name then the property is named you simple use @Column before the property) if you dont want to use the default settings of hiberate on a property that gets mapped.
What if you want to create a property in your POJO which isn't mapped to the database. Then u simple use the @Transient tag which creates a transient object not connected to the session/dadtabase. You can find the list of possible annotations in the hibernate docs: hibernate annotations docs

Next is to alter the sessionFactory in your context xml. The only difference is that i removed the hbm.xml mappings list and added a configurationClass and location for the annotations.
<!-- Session Factory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedClasses">
<list>
<value>data.domain.impl.ProductGroupImpl</value>
<value>data.domain.impl.ProductImpl</value>
</list>
</property>

<property name="hibernateProperties">
<ref bean="hibernateProperties" />
</property>
</bean>
I used a external hibernateProperties bean defined to seperate my hibernateProperties of my tests with those of my live settings.
The AnnotationSessionFactoryBean class is new for annotations and replaces the LocalSessionFactoryBean.

Here is an example how to create a bi-directional many-to-one mapping:
- Product is in 1 ProductGroup.
- A ProductGroup has multiple Products in it

ProductImpl.java:
@ManyToOne(targetEntity = ProductGroupImpl.class)
@JoinColumn(name="productGroupId")
private ProductGroup productGroup;
ProductGroupImpl.java
@OneToMany(targetEntity = ProductImpl.class, mappedBy="productGroup")
private Set<Product> products = new HashSet<Product>();
As you can see i specified the 'JoinColumn' because hibernates make uses the name of your property plus '_id' for the database foreign key. And my foreign key in my database is specified as 'productGroupId'. Furthermore i specified the targetEntity because i used interfaces in my domain classes instead of implementations.

You dont need 'targetEntity' and 'JoinColumn' when you don't use interfaces in your domain and the hibernate table-column naming for foreign keys.

One last issue; you probably have a lot of reoccuring columns in your database. Like your primary key ID and probably a hibernate Version and/or Timestamp column. You don't want to have to create these properties in all of your domain class. The solution is simply creating a baseRecord class containing these properties. You simple mark this class with '@MappedSuperclass' instead of a '@Entity'. This way hibernate won't check for any tables for this superclass. All domain classes that extend this superclass will automaticly also extend the annotations in it!

Annotations rule!!

2 comments:

Anonymous said...

Hey nice blog. Although it�s not what I was looking for. I am looking for info on Payday Loans . I found your blog very interesting

Claudio Fainschtein said...

See here http://tecav.blogspot.com/ how to avoid *.hbm.xml mapping files and use hibernate / ejb3 annotations extending HibernateAssembler as AnnotatedHibernateAssembler