We are the Dev Teams of
  • brands
  • ebay_main
  • ebay
  • mobile
<
>
BLOG

Mixing Annotation Based and XML Mapping in Hibernate

by Michael Herttrich
in Tutorials

Hibernate Symbol With CodeDid you ever work on a Java project using annotation based Hibernate Mapping and wanted to  mix in a class from a different project that is still using a *.hbm.xml file for mapping?

Well, there are two ways to accomplish this:

  • Proselytize all classes in the finalized artifact to the annotion-based way. (For some reasons in our case is touching finalized artifacts is something we really try to avoid.)
  • Get a dependency to the legacy project and try to mix both kinds of mapping within one session factory

So here I will talk about the second way, mixing annotation based and XML Hibernate mapping in one session.

So, imagine in your current terrific project you have some hibernate annotated class Book (just a example, in our reality it was a bit more complex).

@Entity
@Table(name = "book", catalog = "library")
public class Book {

    private Long bookId;
    private String title;
    private Author author;

    // constructor etc.

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "book_id", unique = true, nullable = false)
    public Long getBookId() {
        return this.bookId;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    @Column(name = "title")
    public String getTitle() {
        return this.title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @Cascade({ CascadeType.SAVE_UPDATE })
    @JoinColumn(name = "author_id")
    public Author getAuthor() {
        return this.author;
    }

    public void setAuthor(Author author) {
        this.author = author;
    }
}

In Book you want to refer to class Author as a ManyToOne relationship. Class Author resides in a legacy project. It is a finalized artifact, so you cannot change anything in there (and you don't even want to).

So you just add a dependency to  this artifact in your maven pom file.

NOTE: You might have conflicts in hibernate-core versions between new and legacy. For example if  your old legacy project runs with hibernate-core 3.3.2.GA and classes define some types (i.e. org.hibernate.Hibernate.INTEGER) you cannot update to the newest Hibernate version because these types are removed there.  You cannot even stay with the old Hibernate, because you want  annotations like Entity.  So you need to stay in between, in our case was 3.5.6-Final.

Legacy class Author and its Hibernate mapping in author.hbm.xml

public class Author {

   // constructor here

    Long id;
    String firstname;
    String name;

   // here some plain old GETTER and SETTER 
   // without any annotations
}
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="de.mobile.datastore.legacy.Author" table="author">
		<id name="id" type="long" column="author_id" unsaved-value="-1" access="field">
			<generator class="native"></generator>
		</id>
		<property name="firstname" type="string" column="firstname" access="field" update="false" />
		<property name="name" type="string" column="name" access="field" update="false" />
	</class>
</hibernate-mapping>

In your Hibernate configuration you need to use a AnnotationSessionFactoryBean (extends Local SessionFactoryBean and will be capable of both ways)

With property packagesToScan you  tell hibernate in which package to search for  domain classes. It will consider all classes with @Entity annotion, in our case class Book.

With property annotatedClasses you could even list all persistent domain classes individually.

With property mappingResources we refer to the author.hbm.xml, and it will refer to the Author class. This works even, if Author and its Hibernate mapping are just part of a dependent jar file.

<bean id="readWriteSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="exposeTransactionAwareSessionFactory" value="true" />
        <property name="dataSource">
            <ref bean="readWriteDataSource" />
        </property>
        <property name="hibernateProperties">
            <props>
                <!--here some more hibernateProperties-->
                <prop key="hibernate.cache.provider_configuration_file_resource_path">/ehcache.xml</prop>
                <prop key="hibernate.cache.use_structured_entries">false</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
            </props>
        </property>             
        <property name="packagesToScan" value="de.mobile.library"/>
        <property name="mappingResources">
            <list>
                <value>de/mobile/hibernate/author.hbm.xml</value>
            </list>
        </property>
        <property name="namingStrategy" ref="org.hibernate.cfg.ImprovedNamingStrategy.INSTANCE" />
    </bean>

NOTE:  be careful with hibernate.hbm2ddl.auto, in case of create (even worse drop-create) it will automatically (re)create the schema and  destroying previous data. The suggested value for production databases is validate.

By the way, you can even mix  annotations and hbm mapping within one class. This feature is meant to support the migration process from hbm files to annotations.

class Author with annotations and hbm xml mapping

@Entity
@Table(name = "author", catalog = "library")
public class Author {

    //.....

    @Column(name = "new_name")
    public String getName() {
        return this.name;
    }
<class name="de.mobile.datastore.legacy.Author" table="author">

       ........

       <property name="name" type="string" column="name" access="field"/>
	

The question here is, will Hibernate try to write to column name or column new_name?

It will write to name because the default of hibernate.mapping.precedence is  hbm,class, preferring definitions in the hbm XML mapping.

But you can change it to class,hbm, to prioritize the annotation.

One more thing about Cascading

You might be tempted to define cascading in @ManyToOne because eclipse autocompletion offers it to you, but keep in mind that this is from javax.persistence.

Execution of cascade will fail. You rather need to define org.hibernate.annotations.CascadeType.

//this will not work
import javax.persistence.CascadeType;
.....
@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE })

//rather like this
import org.hibernate.annotations.CascadeType;
......
@ManyToOne(fetch = FetchType.LAZY)
@Cascade({ CascadeType.SAVE_UPDATE })


hibernate, annotations, database, java, programming

?>