Two Classes to One Table
The previous tutorial examined how to annotate a single JavaBean whose properties were mapped to two separate database tables. The converse of that scenario is what we will be looking at in this tutorial. We will look at how we can map two Java classes to a single, common, database table.
One of the most common object-oriented design constructs is the idea that objects are typically things, and those things can be associated with all sorts of extra information that can be factored out into a detail class. In fact, Peter Coad asserts in his groundbreaking book, UML Modeling in Color, that all objects in a problem domain will fall into one of four categories, with the first two being things and thing-details. (Roles and moment-intervals are the other two object types that find their way into a domain model.)
In an object model, a thing is typically associated with its detail object through a one to one mapping. Of course, database administrators don't always see the philosophical benefits of breaking objects mapped together in a one-to-one type of manner into two separate database tables. So, a common need we see in enterprise applications is the need to map two classes that are related in a one to one manner, into a single, monolithic, database table.
The Thing and the ThingDetail
Keeping the example fairly simple so we can concentrate on the Hibernate and JPA implementation, as opposed to other more esoteric Java based concepts, I'm going to create two classes, a Thing class, and a ThingDetail class.
The Thing class maintains a primary key, named id, of type long.
The Thing class also has property of type String called name, and an instance variable of type ThingDetail. Essentially, we can say that a Thing has-a ThingDetail.
The ThingDetail will have two additional properties, an alias of type String, and a count of type int. What are we counting? Who cares, it's just an example!
The Unannotated Thing Class
package com.examscam.mappings;
public class Thing {
private long id;
private String name;
/*A Thing has-a ThingDetail as an instance variable*/
private ThingDetail thingDetail;
public ThingDetail getThingDetail() {
return thingDetail;
}
public void setThingDetail(ThingDetail detail) {
this.thingDetail = detail;
}
/* The primary key id is defined
only in the Thing class*/
public long getId() {return id;}
public void setId(long id) {this.id = id;}
public String getName() {return name;}
public void setName(String n) {this.name = n; }
}
The Unannotated ThingDetail Class
The ThingDetail class is a really simple class with only two instance variables, and curiously, no property to represent a primary key. In fact, since the ThingDetail is entirely contained within the encapsulating Thing class, there is no need for the ThingDetail class to worry about a primary key at all.
package com.examscam.mappings;
public class ThingDetail {
private String alias;
private int count;
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
The Annotated ThingDetail Class
Annotating the ThingDetail class really couldn't be easier. The ThingDetail class is an encapsulated property of the Thing class, and the Thing class will be mapped to a database table named Thing. As a result, we simply need to place an annotation at the top of the ThingDetail class that tells Hibernate that the ThingDetail is going to be embedded inside of another class, and when it comes time to persist the Thing and the ThingDetail, just flatten all of the properties out, and smush them all into a common thing table. A simple @Embeddable
tag does the trick here. Look how simple it is:
package com.examscam.mappings;
import javax.persistence.Embeddable;
@Embeddable
public class ThingDetail {
private String alias;
private int count;
public String getAlias() {return alias;}
public void setAlias(String alias) {
this.alias = alias;
}
public int getCount() {return count;}
public void setCount(int count) {
this.count = count;
}
}
The only difference between the annotated and unannotated ThingDetail class is the introduction of the @Embeddable
tag before the class declaration, along with the import statement that makes it possible to reference the @Embeddable annotation.
@Embedded vs. @Embeddable Annotations
The Thing class is the main entity here, with the ThingDetail being nothing more than a single, simple, embedded property. The Thing class, which I guess you could call the encapsulating class, defines all of the usual tag suspects, including the @Entity tag at the beginning of the class, and the @Id and @GeneratedValue tags before the getId() method.
However, the big distinction with the Thing class, which has an encapsulated property of type ThingDetail, is the fact that the getter for the ThingDetail instance must be decorated with the @Embedded tag. So, the ThingDetail class itself defines a top level @Embeddable annotation, while the getter for the instance variable of type ThingDetail in the encapsulating Thing class must be marked with the @Embedded annotation.
package com.examscam.mappings;
import javax.persistence.*;
import org.hibernate.Session;
import com.examscam.HibernateUtil;
@Entity
public class Thing {
private long id;
private String name;
private ThingDetail thingDetail;
@Embedded
public ThingDetail getThingDetail(){
return thingDetail;
}
public void setThingDetail(ThingDetail detail) {
this.thingDetail = detail;
}
@Id
@GeneratedValue
public long getId() {return id;}
public void setId(long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
Testing the Embedded and Embeddable Code
With all of the required annotations in place, a main method can be coded in the Thing class to test how well the application will work. And remember, what we have is
two Java classes, but only one database table. While we will create two separate classes in our test method, the state of the two JavaBeans will be persisted to a single database table, with a database column defined for each property, all of which is mapped to a single database row with a single, unique, primary key.
public static void main(String args[]) {
HibernateUtil.recreateDatabase();
ThingDetail detail = new ThingDetail();
detail.setAlias("Joey Shabidoo");
detail.setCount(10);
Thing thing = new Thing();
thing.setName("Homer");
thing.setThingDetail(detail);
Session session = HibernateUtil.beginTransaction();
session.save(thing);
HibernateUtil.commitTransaction();
}Notice that in the main method, only the instance of type Thing, sensibly named thing, is passed to the save method of the Hibernate Session. However, since the ThingDetail is associated with the thing being saved through the thing.setThingDetail(detail) method call, well, all of the data in both the instance of the Thing and the encapsulated instance of the ThingDetail will be persisted to the database.
Again, to run the main method, the Thing table must exist in the database, and the AnnotationConfiguration object must be configured with the Thing.class added.
AnnotationConfiguration config =
new AnnotationConfiguration();
config.addAnnotatedClass(Thing.class);
config.configure();
The Whole Thing
package com.examscam.mappings;
import javax.persistence.*;import org.hibernate.Session;
import com.examscam.HibernateUtil;
@Entity
public class Thing {
private long id;
private String name;
private ThingDetail thingDetail;
@Embedded
public ThingDetail getThingDetail(){return thingDetail;}
public void setThingDetail(ThingDetail thingDetail) {
this.thingDetail = thingDetail;
}
@Id
@GeneratedValue
public long getId() {return id;}
public void setId(long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public static void main(String args[]) {
/* recreateDatabase is need to ensure the
Thing table is created */
HibernateUtil.recreateDatabase();
ThingDetail detail = new ThingDetail();
detail.setAlias("Joey Shabidoo");
detail.setCount(10);
Thing thing = new Thing();
thing.setName("Homer");
thing.setThingDetail(detail);
Session session = HibernateUtil.beginTransaction();
/*only the instance of Thing is explicitly saved*/
session.save(thing);
HibernateUtil.commitTransaction();
}
}
Thing, ThingDetail
&AnnotationConfigurations
One of the questions that often gets asked at this point in time is whether or not I've accidentally left out any mention of the ThingDetail class when adding JPA annotated POJOs to the Hibernate Configuration. Well, actually, I haven't forgotten anything.
Certainly, we need to add the Thing.class:
config.addAnnotatedClass(Thing.class);
But you'll notice that we don't have to add the ThingDetail.class. This is true for any Embedded class. When using the @Embedded annotation, only the class that has the actual @Embedded annotation needs to be added to the configuration. The encapsulated class that had the @Embeddable annotation, which in this case is the ThingDetail, does not get added to the configuration. Simply adding the encapsulating class to the configuration is all the information Hibernate needs to go and inspect that embedded class and create columns for the properties the class defines.
public static Configuration getInitializedConfiguration(){
AnnotationConfiguration config =
new AnnotationConfiguration();
/* add all of your JPA annotated classes here!!! */
config.addAnnotatedClass(User.class);
config.addAnnotatedClass(Snafu.class);
config.addAnnotatedClass(FooBar.class);
config.addAnnotatedClass(Thing.class);
config.configure();
return config;
}By the way, just in case you were looking for it, here's the SQL for the creation of the Thing table. Notice the thing table contains fields for the alias and count properties, along with the name and the id.
DROP TABLE IF EXISTS 'examscam'.'thing';
CREATE TABLE 'examscam'.'thing' (
'id' bigint(20) NOT NULL auto_increment,
'name' varchar(255) default NULL,
'alias' varchar(255) default NULL,
'count' int(11) NOT NULL,
PRIMARY KEY ('id'))
Running the Executable main Method
After running the executable main method of the Thing class, Hibernate kicks out an informative little snippet of SQL:
Hibernate: insert into Thing
(name, alias, count) values (?, ?, ?)
A quick inspection of the database indicates that a new record exists in the thing table, with a generated id of 1, a meaningless count of 10, a name of Homer and an alias of Joey Shabidoo.

As you can see, the values written to the thing table are what we would expect from the Thing and ThingDetail objects that were created in the main method of the Thing class:
ThingDetail detail = new ThingDetail();
detail.setAlias("Joey Shabidoo");
detail.setCount(10);
Thing thing = new Thing();
thing.setName("Homer");
thing.setThingDetail(detail);
The Updated HibernateUtil Again
With all of the work we've been doing, I just thought I'd print out the HibernateUtil class in its entirety again.
public class HibernateUtil {
private static SessionFactory factory;
public static Configuration getInitializedConfiguration() {
AnnotationConfiguration config =
new AnnotationConfiguration();
/* add all of your JPA annotated classes here!!! */
config.addAnnotatedClass(User.class);
config.addAnnotatedClass(Snafu.class);
config.addAnnotatedClass(FooBar.class);
config.addAnnotatedClass(Thing.class);
config.configure();
return config;
}
public static Session getSession() {
if (factory == null) {
Configuration config =
HibernateUtil.getInitializedConfiguration();
factory = config.buildSessionFactory();
}
Session hibernateSession = factory.getCurrentSession();
return hibernateSession;
}
public static void closeSession(){
HibernateUtil.getSession().close();
}
public static void recreateDatabase() {
Configuration config;
config = HibernateUtil.getInitializedConfiguration();
new SchemaExport(config).create(true, true);
}
public static Session beginTransaction() {
Session hibernateSession;
hibernateSession = HibernateUtil.getSession();
hibernateSession.beginTransaction();
return hibernateSession;
}
public static void commitTransaction() {
HibernateUtil.getSession().getTransaction().commit();
}
public static void rollbackTransaction() {
HibernateUtil.getSession().getTransaction().rollback();
}
}
|