Introduction

This JDK5 annotation library, eliminates hand-written (or XDoclet generated) Hibernate mapping (*.hbm.xml) files. All annotation elements are chosen to be as close as possible to their mapping xml counter part. Please, read the Hibernate docs for explanations on how to use the elements.

Simple Example

Annotating an entity class

The Person class below shows the simplest possible annotated entity class.

@clazz
public class Person {
    public Person() {}
    public Person(String name) {this.name = name;}

    @id
    private Long        id;
    public Long         getId() {return id;}
    public void         setId(Long id) {this.id = id;}

    @property
    private String      name;
    public String       getName() {return name;}
    public void         setName(String name) {this.name = name;}
}
                

The clazz annotation, corresponds to a class XML node in a mapping file. As you can see, there have been some minor deviations from the XML names, simply because they clash with reserved words in Java.

The id and property annotations have their usual meaning. All annotations have many attributes, corresponding to attributes or nested elements of their XML node counter parts. However, the annotation attributes all have sensible default values, either hard coded or extracted from the class/field information.

Hibernate loading

A Hibernate mapping XML is generated behind the scene, when a class is added to a Hibernate configuration. The snippet below loads the Person class into a Hibernate configuration object. The Configuration object is a subclass to Hibernate's ditto.

Configuration  cfg = new Configuration();
cfg.addAnnotatedClass( Person.class );
//...load Hibernate props...
SessionFactory  sf = cfg.buildSessionFactory();

The mapping XML generated in the example above, is shown below.

<hibernate-mapping>
  <class name="com.ribomation.metahibernate.functest.polymorphicAssociation.Person"
         table="PERSONS" optimistic-lock="version" mutable="true"
         dynamic-update="false" dynamic-insert="false"
         select-before-update="false" polymorphism="true"
         lazy="false" batch-size="1">
    <id name="id" column="ID" access="property" unsaved-value="null">
      <generator class="native"/>
    </id>
    <property name="name" column="NAME" access="property" insert="true" update="true"/>
  </class>
</hibernate-mapping>

Less Simple Example

At less simple example, although still non-complex, is the first functional test of this library. You can find the complete source code in Polymorphic Association Functional Test.

This test demonstrates a polymorphic bidirectional 1-M assocation from a Person entity to a Vehicle entity hierarchy.

@clazz
public class Person {
    public Person() {}
    public Person(String name) {
        this.name = name;
    }

    @id
    private Long        id;
    public Long         getId() {return id;}
    public void         setId(Long id) {this.id = id;}

    @property
    private String      name;
    public String       getName() {return name;}
    public void         setName(String name) {this.name = name;}

    @set(oneToMany = Vehicle.class, cascade = CascadeType.saveUpdate, lazy = false)
    private Set     vehicles = new HashSet();
    public Set      getVehicles() {return vehicles;}
    public void     setVehicles(Set vehicles) {this.vehicles = vehicles;}
    public void     addVehicle(Vehicle v) {
        getVehicles().add(v);
        v.setOwner(this);
    }
}

@clazz
@discriminator(column = "type")
public abstract class Vehicle {
    protected Vehicle() {}
    protected Vehicle(int speed) {
        this.speed = speed;
    }

    @id
    private Long        id;
    public Long         getId() {return id;}
    public void         setId(Long id) {this.id = id;}

    @property
    private int     speed;
    public int      getSpeed() {return speed;}
    public void     setSpeed(int speed) {this.speed = speed;}

    @manyToOne(column = "person_fk")
    private Person      owner;
    public Person getOwner() {return owner;}
    public void setOwner(Person owner) {this.owner = owner;}
}

@subclass
public class Car extends Vehicle {
    public Car() {}
    public Car(String regNr) {
        super(110);
        this.regNr = regNr;
    }

    @property
    private String      regNr;
    public String       getRegNr() {return regNr;}
    public void         setRegNr(String regNr) {this.regNr = regNr;}
}

@subclass
public class Boat extends Vehicle {
    public Boat() {}
    public Boat(boolean motorized) {
        super(45);
        this.motorized = motorized;
    }

    @property
    private boolean     motorized = true;
    public boolean      isMotorized() {return motorized;}
    public void         setMotorized(boolean motorized) {this.motorized = motorized;}
}
            

The classes are loaded into Hibernate using the snippet below.

Class[]  classes = {
    Person.class,
    Vehicle.class, Car.class, Boat.class
};

public void    test_hsqldb() throws HibernateException {
    Configuration  cfg = new Configuration();
    for (Class c : classes) { cfg.addAnnotatedClass(c); }
    cfg.setProperties( createHsqlProps() );

    new SchemaExport(cfg).create(true, true);

    SessionFactory     sf = cfg.buildSessionFactory();
    HibernateTemplate  h  = new HibernateTemplate(sf);

    Person  p = new Person("Sham Poo");
    h.save(p);
    final Long  id = p.getId();
    Person  q = (Person) h.get(Person.class, id);

    h.execute(new HibernateCallback() {
        public Object doInHibernate(Session s) throws HibernateException, SQLException {
            Person  w = (Person) s.get(Person.class, id);
            w.addVehicle( new Car("ABC123") );
            w.addVehicle( new Boat(true) );
            s.update(w);
            return null;
        }
    });

    List<Person>  persons = (List<Person>) h.find("from "+Person.class.getName());
    sf.close();

    assertEquals("persons.size", 1, persons.size());
    q = persons.get(0);
    assertEquals("q.vehicles().size", 2, q.getVehicles().size());
}

protected Properties  createHsqlProps() {
    Properties props = new Properties();
    props.setProperty(Environment.DIALECT , HSQLDialect.class.getName());
    props.setProperty(Environment.DRIVER  , org.hsqldb.jdbcDriver.class.getName());
    props.setProperty(Environment.URL     , "jdbc:hsqldb:mem:foobar");
    props.setProperty(Environment.USER    , "sa");
    props.setProperty(Environment.PASS    , "");
    return props;
}

Annotation Tags

Implementing all mapping XML tags, is in progress. The current "vertical slice" release do not define all tags yet. The tag set implemented so far is listed below.

@Hibern8 TagHibernate XML TagDescription
clazzclassAnnotates an entity class.
ididID property.
propertypropertyOrdinary property.
componentcomponentIdentifies a component.
subclasssubclassDefines an entity subclass.
discriminatordiscriminatorIdentifies a subclass.
mappinghibernate-mappingRoot node a mapping XML. Generated by default, but can be used to define specific attributes.
manyToOnemany-to-oneThe 'many' side of an association.
setsetThe 'one' side of an one2many association
parentparentUsed to mark a property, inside a component class, as the link back to its parent.
paramparamUsed as a <param name="key">value</param> node.

Future improvements

  • Implement all mapping XML tags in Hibernate 2.
  • Change the internal implementation to not generate the XML explicitly.