Advanced Mappings
Having provided a number of fairly simple
and straight forward tutorials of how Hibernate can be used to
implement data mappings, I thought that it would be nice to take a
look at a slightly more complex example that combines all of the most
common mapping patterns.
The goal of this section, is to work
towards the implementation of a fairly common Human Resources or Job
Placement application in which clients must register, provide their
primary home address, along with any work or business addresses, and
finally, from a list of skills, the client must select the ones that
most closely match their individual competencies. Basically, we're
going to be working towards the implementation of a skills management
type of application.
The design model will place central focus
on a Client class, with the client having a one-to-one relationship
with a ClientDetail class, a one-to-many relationship with an Address
class, and finally, a many to many relationship with a Skill class.
Furthermore, all of the relationships will be bi-directional.
Discovering the Solution
As you could imagine, the mappings
contained in the Client class will get pretty hairy, but as with all
J2EE applications, if you break things down into their individual
components and tackle the problem one step at a time, the whole
solution will quickly and seamlessly fall into place.
Implementing the Domain Model
The first step towards putting together a
solution for the Skills Manager application is to create a basic
skeleton for each of the classes involved in the domain model. The
Client class will reference each class in the domain, so without a
compiled class definition for each component, you'll end up with a
bunch of compile errors as you begin to build the Client class.
Here are the basic, codeless, class
declarations for the POJOs we'll be using to create the Skills Manager
application:
package com.examscam.model;
public class Client {}
package com.examscam.model;
public class ClientDetail {}
package com.examscam.model;
public class Address {}
package com.examscam.model;
public class Skill {}
Of course, each public class must be
defined in its own .java file, all of which should be saved in a
directory structure consistent with the package name of
com.examscam.model.
Class Diagram for the Skills Manager
App
Adding Properties and Methods
After the basic class files have been
created and compiled, it's time to take a look at the object model
that has been developed, and start coding the basic properties and
methods required to implement our domain. For this iteration, we will
just concentrate on adding the appropriate properties and
setter/getter methods. When the basic class structures have been
coded, we will begin to add the appropriate and required JPA
annotations.
Properties of the Client Class
Without any JPA annotations, the Client
class will define four internal properties of its own:
-->two String properties for the
username and password
--> a Boolean value to indicate
whether the client is verified or not
-->an id field of type Long
Implementing Associations with the
Client
As for implementing the associations
between the Skill, Address and Client Detail classes, the Client class
will define:
--> an instance variable of type
ClientDetail
-->an instance variable of type
java.util.List to contain the many Address objects a client might have
-->an instance variable of type
java.util.List to maintain the many Skill objects a client might have
package com.examscam.model;
import java.util.List;import java.util.Vector;
public class Client {
private List<Address> addresses = new Vector<Address>();
private List<Skill> skills = new Vector<Skill>();
private ClientDetail clientDetail;
private Long id;
private String username;
private String password;
private Boolean verified;
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public List<Skill> getSkills() {return skills;}
public void setSkills(List<Skill> skills) {
this.skills = skills;
}
public List<Address> getAddresses() {return addresses;}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
public ClientDetail getClientDetail(){return clientDetail;}
public void setClientDetail(ClientDetail clientDetail) {
this.clientDetail = clientDetail;
}
public String getPassword() {return password;}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {return username;}
public void setUsername(String username) {
this.username = username;
}
public Boolean getVerified() {return verified;}
public void setVerified(Boolean verified) {
this.verified = verified;
}
}
The ClientDetail Class
All of the really yummy information about
a client is held in the ClientDetail class. The ClientDetail class
defines five properties of type String, namely:
-->a firstName
-->a middleName
-->a lastName
-->an emailAddress
-->a password
-->a password hint
Furthermore the ClientDetail class will
define two java.sql.Date objects to maintain the registrationDate and
the verificationDate. The id will quite unspectacularly be maintained
as an instance variable of type Long.
Implementing the Association in Java
Of course, the ClientDetail class is
associated with a Client, so, the ClientDetail class defines an
instance variable of type Client, which will quite logically be named:
client.
Unannotated Code for the ClientDetail
Class
package com.examscam.model; import java.sql.*;
public class ClientDetail {
private Long id;
private Client client;
private String firstName, middleName, lastName;
private String emailAddress, passwordHint;
private Date registrationDate, verificationDate;
public Client getClient(){return client;}
public void setClient(Client client){this.client=client;}
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getEmailAddress() {return emailAddress;}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getFirstName() {return firstName;}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {return lastName;}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getMiddleName() {return middleName;}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getPasswordHint() {return passwordHint;}
public void setPasswordHint(String passwordHint) {
this.passwordHint = passwordHint;
}
public Date getRegistrationDate() {return registrationDate;}
public void setRegistrationDate(Date registrationDate) {
this.registrationDate = registrationDate;
}
public Date getVerificationDate() {return verificationDate;}
public void setVerificationDate(Date verificationDate) {
this.verificationDate = verificationDate;
}
}
The Address Class
The address class looks somewhat like a
unified theory of the sub-particle universe, as it's just loaded with
Strings. With the exception of the id of type Long, and
the ever important reference to the associated Client, the Address
class contains the following six variables of type String:
-->addressLine1
-->addressLine2
-->city
-->state
-->country
-->code (for the zip code or
postal code)
It's worth mentioning again that the
relationship between the Address and the Client is bidirectional, so
the Client class maintains a collection of Address objects through a
java.util.List, while the Address class links back to the owning class
through an instance variable of type Client.
package com.examscam.model;
public class Address {
private Long id;
private Client client;
private String addressLine1;
private String addressLine2;
private String city;
private String state;
private String country;
private String code;
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public Client getClient() {return client;}
public void setClient(Client c) {this.client = c;}
public String getAddressLine1() {return addressLine1;}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {return addressLine2;}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getCity() {return city;}
public void setCity(String city) {this.city = city;}
public String getCountry() {return country;}
public void setCountry(String c) {this.country = c;}
public String getCode() {return code;}
public void setCode(String code) {this.code = code;}
public String getState() {return state;}
public void setState(String state) {
this.state = state;
}
}
The Skill Class
The Skill class, which shares a
many-to-many relationship with the Client class, is the simplest Java
class we have in this problem domain. The Skill class simply defines
an id of type Long, and a name for the Skill in question of type
String.
The collection of Skills itself is
implemented as a java.util.Set, as opposed to a java.util.List. The
nice thing about a Set, as opposed to a List, is that it can't contain
duplicate entries, which makes sense for the set of Skills our
application will display.
Unannotated Code for the Skill Class
package com.examscam.model;
import java.util.Set;
public class Skill {
private Long id;
private String name;
private Set<Client> clients;
public Set<Client> getClients() {
return clients;
}
public void setClients(Set<Client> clients) {
this.clients = clients;
}
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;
}
}

The Client to ClientDetail Relationship
With the problem domain classes coded
with instance variables and public setter and getter methods, it's
time to start adding in some JPA annotations.
The simplest of all the relationships is
the one-to-one, bidirectional relationship between the Client and the
ClientDetail. Preparing the Client class to participate in Java
Persistence, we must first add the appropriate @Entity and @Table
annotations, followed by the correct decorations needed for the getter
of the primary key.
import java.util.*;
import javax.persistence.*;
@Entity
@Table(name = "client", schema = "examscam")
public class Client {
@Id
@GeneratedValue
@Column(name = "id")
public Long getId() { return id; }
}
Of course, the most pertinent annotation
of interest happens when we place the @OneToOne annotation above the
getClientDetail() method:
@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
@JoinColumn(name="detail_id")
public ClientDetail getClientDetail() {
return clientDetail;
}
Notice that the owning class, the Client,
defines the name of the join column, which turns into a foreign key in
the client database table, allowing the Client to be able to link to
the corresponding ClientDetail class. The @OneToOne annotation
describes the fact that every class has one and only one ClientDetail
object. Furthermore, the fetch type, which defaults to active, is
over-ridden, as it has been set to FetchType.LAZY.
The ClientDetail to Client Relationship
The ClientDetail class maps the opposite
end of the one-to-one relationship owned by the Client class. Notice
in the ClientDetail's @OneToOne annotation that the mappedBy attribute
references the name of the instance variable of type ClientDetail that
is used by the Client class. The ClientDetail side of the relationship
does not need to define the @JoinColumn, as that is defined in the
Client class through the getter for the instance variable named
clientDetail. However, on ClientDetail side of the relationship, we
need to specify the name of that ClientDetail instance on the owning
side through the mappedBy attribute, as shown below:
import javax.persistence.*;
@Entity
@Table(name = "client_detail", schema = "examscam")
public class ClientDetail {
@Id
@GeneratedValue
@Column(name = "id")
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
@OneToOne(cascade=CascadeType.ALL,
mappedBy="clientDetail")
public Client getClient(){return client;}
public void setClient(Client c){client=c;}
}
It is also worth pointing out that the
database table name will not be simply mapped by the name of the
class, but instead, true to database naming conventions, an underscore
will separate the word client and detail, making the table name
?client_detail,? as seen in the @Table annotation.
The Client Has Many Address Objects
After getting the one-to-one Client to
ClientDetail association implemented with JPA annotations, it's time
to move onto the OneToMany relationship between the Client and Address
object. A client can have many address objects, which means the
getAddress() method in the client class must be decorated with a
@OneToMany annotation:
@Entity
@Table(name = "client", schema = "examscam")
public class Client {
@OneToMany(mappedBy="client",
targetEntity=Address.class,
fetch=FetchType.EAGER,
cascade = CascadeType.ALL)
public List<Address> getAddresses() {
return addresses;
}
}
The @OneToMany mapping from the owning
class (Client) to the owned class (Address) is relatively straight
forward, with the standard fetch and cascade attributes defined as
EAGER and ALL respectively. As for the mappedBy attribute, this is the
name of the instance variable used by the Address class to define an
instance variable of type Client. Since the Address class will define
the @JoinColumn, the Client class will need to specify the name of the
instance variable in the Address class used to navigate the opposite
end of the mapping.
Additionally, the @OneToMany mapping
defines a targetEntity attribute which explicitly defines the type of
the class (Address.class) representing the many side of the OneToMany
relationship.
package com.examscam.model;
import javax.persistence.*;
@Entity
@Table(name = "address", schema = "examscam")
public class Address {
private Long id;
private Client client;
private String addressLine1;
private String addressLine2;
private String city, state, country, code;
@Id
@GeneratedValue
@Column(name = "id")
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
@ManyToOne
@JoinColumn(name="client_id")
public Client getClient() {return client;}
public void setClient(Client c) {this.client = c;}
@Column(name = "addr1", nullable=true)
public String getAddressLine1() {return addressLine1;}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
@Column(name = "addr2", nullable=true)
public String getAddressLine2() {return addressLine2;}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getCity() {return city;}
public void setCity(String city) {this.city = city;}
public String getCountry() {return country;}
public void setCountry(String c) {this.country = c;}
public String getCode() {return code;}
public void setCode(String code) {this.code = code;}
public String getState() {return state;}
public void setState(String state) {
this.state = state;
}
}
The Address Class Annotations
The Address class implements the many
side of the relationship between the Client and the address, and as
such, it is decorated with the @ManyToOne annotation.
The many side of a relationship is also
responsible for defining a foreign key that maps back to the primary
key of the owning class. The name of this field is defined through the
name attribute of the @JoinColumn annotation, as evidenced below:
@ManyToOne
@JoinColumn(name="client_id")
public Client getClient() {return client;}
public void setClient(Client c) {this.client = c;}
It should also be pointed out that the
addressLine1 and the addressLine2 properties are mapped to database
columns with slightly different names, namely addr1 and addr2
respectively. This is evidenced by the @Column annotations above the
corresponding getter methods:
@Column(name = "addr1", nullable=true)
public String getAddressLine1() {return addressLine1;}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
@Column(name = "addr2", nullable=true)
public String getAddressLine2() {return addressLine2;}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
Completing the annotations for the
Address class are the decorations above the class declaration and the
getId() method:
import javax.persistence.*;
@Entity
@Table(name = "address", schema = "examscam")
public class Address {
@Id
@GeneratedValue
@Column(name = "id")
public Long getId() {return id;}
} The Client Has Skills
ManyToMany relationships always tend to
be the most annotation intensive associations to map. On the Client
side of the relationship, we will define the @ManyToMany annotation
above the getSkills() method.
Further detail about the ManyToMany
relationship is described in the @JoinTable annotation. This
annotation describes the fact that a new table, named client_skill,
will be used to keep track of the primary keys of instances of both
the Client and Skill classes that are associated with each other.
Furthermore, the embedded @JoinColumn tells us that the primary key of
the client will be held in a column named client_id, and the inverse
side of the relationship will be maintained by storing the primary key
of the instance of the associated Skill class in a column of the
client_skill table named skill_id.
@ManyToMany /***** From the Client Class *****/
@JoinTable( name = "client_skill",
joinColumns = { @JoinColumn(name = "client_id") },
inverseJoinColumns={ @JoinColumn(name = "skill_id") })
public List<Skill> getSkills() {
return skills;
}
It can all seem somewhat intimidating,
but really, the @JoinTable annotation is simply defining the name of
the join table, along with the names of the two columns in that join
table.
A Skill is Associated with Many Clients
Of course, the same type of ManyToMany
mapping needs to be made in the Skill class, mapping the Skill back to
the many Clients with which it might be associated. As you can see,
the mapping is very similar:
/***** From the Skill Class *****/
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "client_skill",
joinColumns={@JoinColumn(name="skill_id")},
inverseJoinColumns={
@JoinColumn(name = "client_id") })
public Set<Client> getClients() {return clients;}
The Skill Class
Along with the @ManyToMany annotation
above the getClients() method, which is essentially the exact reverse
of the @ManyToMany annotation defined above the getSkills() method in
the Client class, the Skill class also requires the requisite
annotations to define the class as an entity, and to define the id
field as the primary key for the class. Here's the code for the Skill
class:
package com.examscam.model;
import java.util.Set;
import javax.persistence.*;
@Entity
@Table(name = "skill", schema = "examscam")
public class Skill {
private Long id;
private String name;
private Set<Client> clients;
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable ( name = "client_skill",
joinColumns = { @JoinColumn ( name = "skill_id") },
inverseJoinColumns = {
@JoinColumn( name = "client_id" )
})
public Set<Client> getClients() {return clients;}
public void setClients(Set<Client> c) {clients=c;}
@Id
@GeneratedValue
@Column(name = "id")
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;}
}
The Completely Annotated Client Class
package com.examscam.model;
import java.util.*;import javax.persistence.*;
@Entity
@Table(name = "client", schema = "examscam")
public class Client {
private List<Address> addresses = new Vector<Address>();
private List<Skill> skills = new Vector<Skill>();
private ClientDetail clientDetail;
private Long id;private String username;
private String password;private Boolean verified;
@Id
@GeneratedValue
@Column(name = "id")
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
@ManyToMany
@JoinTable(name = "client_skill",
joinColumns = { @JoinColumn(name = "client_id") },
inverseJoinColumns = { @JoinColumn(name = "skill_id") })
public List<Skill> getSkills() {return skills;}
public void setSkills(List<Skill> skills){this.skills=skills;}
@OneToMany(mappedBy="client", targetEntity=Address.class,
fetch=FetchType.EAGER, cascade = CascadeType.ALL)
public List<Address> getAddresses() {return addresses;}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
@JoinColumn(name="detail_id")
public ClientDetail getClientDetail(){return clientDetail;}
public void setClientDetail(ClientDetail clientDetail) {
this.clientDetail = clientDetail;
}
public String getPassword() {return password;}
public void setPassword(String password){this.password = password;}
public String getUsername() {return username;}
public void setUsername(String username) {
this.username = username;
}
public Boolean getVerified() {return verified;}
public void setVerified(Boolean verified){this.verified=verified;}
}
Creating the Required Database Tables
Finally, with all of the classes, coded,
you must make sure that any Hibernate Session that is used to manage
the persistence of these objects has the various classes added to the
configuration. The best place to do this is in the HibernateUtil class
that we defined in an earlier chapter. The initialization of the
AnnotationConfiguration would look something like this:
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.addAnnotatedClass(LeftManyStudent.class);
//config.addAnnotatedClass(RightManyCourse.class);
config.addAnnotatedClass(Client.class);
config.addAnnotatedClass(ClientDetail.class);
config.addAnnotatedClass(Address.class);
config.addAnnotatedClass(Skill.class);
config.configure();
return config;
}
Adding these four new classes to the
AnnotationConfiguration object, and then running the
recreateDatabase() method of the HibernateUtil class would then create
the underlying tables needed to support this JPA annotated domain
model.


|