Introduction
EJB 3.0 annotations can be used to define relationships between entity
EJB's. ManyToMany relationships can cause some confusion at first.
This article explains some of the considerations using a simple
example.
JBoss 4.0.4 GA, MySQL and Eclipse were used to develop
this example. The option of creating and dropping the tables was
used (defined in the file persistence.xml). By allowing the EJB
container to create and drop the database tables, it is possible
to run the application and then inspect the database to see how
the entities have been mapped to database tables.
The source code can be downloaded as a zip
archive here.
Entities
The following EJB3 entities are coded:
- Home
- Person
- Role
- UserRole
Home Entity
Home has a many to many relationship with Person. A person can have
zero or more homes, and a home can have zero or more persons.
package com.j3ltd.server.ejb;
import java.io.Serializable;
import javax.persistence.*;
import java.util.*;
@Entity
@NamedQueries({
@NamedQuery(name="findHomeAll", query="SELECT o FROM Home o"),
@NamedQuery(name="findHomeByStreetAddress",
query="SELECT o FROM Home o WHERE o.streetAddress = :streetAddress")
})
public class Home implements Serializable {
static final long serialVersionUID = 1;
private int id;
private String streetAddress;
private List<Person> persons;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getStreetAddress() {
return streetAddress;
}
public void setStreetAddress(String streetAddress) {
this.streetAddress = streetAddress;
}
@ManyToMany
@JoinTable(name="HomePersons",
joinColumns=
@JoinColumn(name="homeId", referencedColumnName="id"),
inverseJoinColumns=
@JoinColumn(name="personId", referencedColumnName="id")
)
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
public void addPerson(Person toAdd) {
this.getPersons().add(toAdd);
}
public void dropPerson(Person toDrop) {
this.getPersons().remove(toDrop);
}
}
Named queries are placed at the start of the class. They are used by
the stateless session facade bean. Named queries can be placed
in a variety of places, placing them in the entity class on which
they operate is one way.
A serialVersionUIDc is given
to all entities, otherwise passing a List (or Collection) to the web
application client can result in a class cast exception. Each
time a change is made to this entity, the version number is incremented.
The primary key id is generated by MySQL.
The many to many relationship ensures that when a person or a
home is deleted, no cascade delete is performed. For example, the
dropPerson() method only removes the relationship between this
home entity and the person. The person entity is not removed.
Person Entity
Person has a one to many relationship with UserRole. A person can have
zero or more UserRoles's. A user role has one person associated
with it.
package com.j3ltd.server.ejb;
import java.io.Serializable;
import javax.persistence.*;
import java.util.*;
@Entity
@NamedQueries({
@NamedQuery(name="findPersonAll", query="SELECT o FROM Person o"),
@NamedQuery(
name="findPersonByName", query="SELECT o FROM Person o WHERE o.name = :name")
})
public class Person implements Serializable {
static final long serialVersionUID = 2;
private String name;
private int id;
private List<Home> homes;
private List<UserRole> userRoles;
@ManyToMany(mappedBy="persons")
protected List<Home> getHomes() {
return homes;
}
protected void setHomes(List<Home> homes) {
this.homes = homes;
}
@OneToMany(cascade=CascadeType.ALL, mappedBy="person")
public List<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(List<UserRole> userRoles) {
this.userRoles = userRoles;
}
public void addUserRole(UserRole userRole) {
this.getUserRoles().add(userRole);
userRole.setPerson(this);
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
The many to many is defined here for the Home. MappedBy specifies which
field in the home returns the persons.
A one to many relationship is defined for the UserRole entity.
It is worth noting that the addUserRole() method adds the user role
to its list of user roles, and also calls setPerson() on the user
role. Failure to do the latter would cause the UserRole table to
have a null value for the person id foreign key.
UserRole Entity
UserRole has a many to one relationship with Role. A Role can have zero
or more UserRole's, but a UserRole has only one Role.
package com.j3ltd.server.ejb;
import java.io.Serializable;
import javax.persistence.*;
@Entity
public class UserRole implements Serializable {
static final long serialVersionUID = 1;
private int id;
private Role role;
private Person person;
public UserRole() {
}
public UserRole(Role role) {
this.role = role;
}
@ManyToOne
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@ManyToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn(name="roleName",
referencedColumnName="roleName")
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
Role Entity
package com.j3ltd.server.ejb;
import java.io.Serializable;
import javax.persistence.*;
import java.util.*;
@Entity
public class Role implements Serializable {
static final long serialVersionUID = 1;
private String roleName;
private String roleDescription;
private List<UserRole> userRoles;
public String getRoleDescription() {
return roleDescription;
}
public void setRoleDescription(String roleDescription) {
this.roleDescription = roleDescription;
}
@Id
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
@OneToMany(mappedBy="role")
public List<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(List<UserRole> userRoles) {
this.userRoles = userRoles;
}
}
Entity Relationships
In essence the Home - Person is a many to many relationship, where the
link table is left to the EJB container to work out. The Person
- Role relationship, is also a many to many relationship, but in
this case, the link table is implemented manually, and the relationships
are broken down using one to many and many to one relationships.
Below is the diagram of the resulting database tables.

Stateless Session Facade
A stateless session bean is coded, this bean is
used by the client to perform the database operations.
package com.j3ltd.server.ejb;
import javax.ejb.*;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.*;
@Stateless public class StatelessEJBBean implements StatelessEJB {
@PersistenceContext(unitName="EJBRelationshipsPU") EntityManager em;
public List<Person> getAllPersons() {
ArrayList<Person> toReturn = new ArrayList<Person>();
Query q = em.createNamedQuery("findPersonAll");
for (Object po : q.getResultList()) {
toReturn.add((Person) po);
}
return toReturn;
}
public List<Home> getAllHomes() {
ArrayList<Home> toReturn = new ArrayList<Home>();
Query q = em.createNamedQuery("findHomeAll");
for (Object ho : q.getResultList()) {
toReturn.add((Home) ho);
}
return toReturn;
}
/**
* Client can't call getHomes() on person, as it's detached &
* lazilly fetched
*/
public List<Home> getPersonHomes(Person person) {
em.refresh(person);
List<Home> homes = person.getHomes();
ArrayList<Home> toReturn = new ArrayList<Home>(homes.size());
for (Home home : homes) {
toReturn.add(home);
}
return toReturn;
}
/**
* Client can't call getPersons() on home, as it's detached &
* lazilly fetched
*/
public List<Person> getHomePersons(Home home) {
em.refresh(home);
List<Person> persons = home.getPersons();
ArrayList<Person> toReturn = new ArrayList<Person>(persons.size());
for (Person person : persons) {
toReturn.add(person);
}
return toReturn;
}
public List<UserRole> getPersonRoles(Person person) {
em.refresh(person);
List<UserRole> roles = person.getUserRoles();
ArrayList<UserRole> toReturn = new ArrayList<UserRole>(roles.size());
for (UserRole role : roles) {
toReturn.add(role);
}
return toReturn;
}
public void createTestData() {
// Create the role entities
Role pm = new Role();
pm.setRoleName("pm");
pm.setRoleDescription(("Prime Minister"));
em.persist(pm);
Role pres = new Role();
pres.setRoleName("pres");
pres.setRoleDescription("President");
em.persist(pres);
Role vp = new Role();
vp.setRoleName("vp");
vp.setRoleDescription("Vice President");
em.persist(vp);
// create home 10 downing street
Home home = new Home();
home.setStreetAddress("10 Downing Street");
em.persist(home);
// need to refresh otherwise home.addPerson() fails:
// persons List member is null.
em.refresh(home);
// create mr blair
Person person = new Person();
person.setName("Mr Blair");
em.persist(person);
// add user role pm
UserRole userRole = new UserRole(pm);
em.persist(userRole);
em.refresh(person);
person.addUserRole(userRole);
// add user role pres
userRole = new UserRole(pres);
em.persist(userRole);
person.addUserRole(userRole);
home.addPerson(person);
// create Mr Brown
person = new Person();
person.setName("Mr Brown");
em.persist(person);
em.refresh(person);
home.addPerson(person);
// add user role vp
userRole = new UserRole(vp);
em.persist(userRole);
person.addUserRole(userRole);
// create 11 downing street
home = new Home();
home.setStreetAddress("11 Downing Street");
em.persist(home);
em.refresh(home);
// add current person Mr Brown
home.addPerson(person);
// create whitehouse
home = new Home();
home.setStreetAddress("Whitehouse, Washington DC");
em.persist(home);
em.refresh(home);
// add Mr Bush
person = new Person();
person.setName("Mr Bush");
em.persist(person);
em.refresh(person);
// Add user role pres
userRole = new UserRole(pres);
em.persist(userRole);
person.addUserRole(userRole);
home.addPerson(person);
// create mr Cheney
person = new Person();
person.setName("Mr Cheney");
em.persist(person);
em.refresh(person);
// add user role vp
userRole = new UserRole(vp);
em.persist(userRole);
person.addUserRole(userRole);
home.addPerson(person);
// create Élisée Palace
home = new Home();
home.setStreetAddress("Élisée Palace");
em.persist(home);
em.refresh(home);
// create Mr Chirac
person = new Person();
person.setName("Mr Chirac");
em.persist(person);
em.refresh(person);
// add user role pres
userRole = new UserRole(pres);
em.persist(userRole);
person.addUserRole(userRole);
home.addPerson(person);
}
public void deleteSomeData() {
// remove 11 downing street
Query q = em.createNamedQuery("findHomeByStreetAddress");
q.setParameter("streetAddress", "11 Downing Street");
List list = q.getResultList();
for (Object home : list) {
em.remove(home);
}
// remove Mr blair
q = em.createNamedQuery("findPersonByName");
q.setParameter("name", "Mr Blair");
list = q.getResultList();
for (Object person : list) {
removePerson((Person)person);
}
// remove Mr Chirac
q.setParameter("name", "Mr Chirac");
list = q.getResultList();
for (Object person : list) {
removePerson((Person)person);
}
}
/**
* To remove a Person record, the person must be removed from
* all the homes first. Otherwise a foreign key constraint
* is thrown when removing the person.
*
* @param toRemove the person to remove.
*/
private void removePerson(Person toRemove) {
List<Home> homes = toRemove.getHomes();
for (Home home : homes) {
home.dropPerson(toRemove);
}
em.remove(toRemove);
}
}
Servlet
A servlet is coded to act as a test client.
package com.j3ltd.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.j3ltd.server.ejb.*;
public class RelationshipServlet extends HttpServlet {
private StatelessEJB statelessBean;
public void init() throws ServletException {
try {
Context context = new InitialContext();
statelessBean = (StatelessEJB) context.lookup("StatelessEJBBean/remote");
} catch (NamingException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.write("Create test data...");
statelessBean.createTestData();
writer.write("done\n\n");
displayPeople(resp);
displayHomes(resp);
writer.write("\nDelete some data...");
statelessBean.deleteSomeData();
writer.write("done\n\n");
displayPeople(resp);
displayHomes(resp);
}
private void displayPeople(HttpServletResponse resp) throws IOException {
PrintWriter writer = resp.getWriter();
writer.write("List of Person ejbs:\n");
List<Person> people = statelessBean.getAllPersons();
for (Person person : people) {
writer.write("Person retrieved: " + person.getName() + "\n");
List<UserRole> roles = statelessBean.getPersonRoles(person);
if (roles != null) {
writer.write(" role: ");
for (UserRole role : roles) {
writer.write(role.getRole().getRoleName() + " ");
}
writer.write("\n");
}
List<Home> homes = statelessBean.getPersonHomes(person);
if (homes != null) {
for (Home home : homes) {
writer.write(" Home: " + home.getStreetAddress() + "\n");
}
}
}
}
private void displayHomes(HttpServletResponse resp) throws IOException {
PrintWriter writer = resp.getWriter();
writer.write("\nList of Home ejbs:\n");
List<Home> homes = statelessBean.getAllHomes();
for (Home home : homes) {
writer.write("Home retrieved: " + home.getStreetAddress() + "\n");
List<Person> people = statelessBean.getHomePersons(home);
if (people != null) {
for (Person person : people) {
writer.write(" Person: " + person.getName() + "\n");
List<UserRole> roles = statelessBean.getPersonRoles(person);
if (roles != null) {
writer.write(" role: ");
for (UserRole role : roles) {
writer.write(role.getRole().getRoleName() + " ");
}
writer.write("\n");
}
}
}
}
}
}
Packaging And Deployment
The project is created
as an EJB3 JBoss IDE project.:
The libraries should look like the following:
The J2EE 1.5 JBoss IDE library was added to the
project, so that servlets are supported.
The packaging configuration creates a jar file for
the EJB's and a war file for the web client.
In MySQL a database schema is created. See here,
for an overview of this process. The database is called ejbrelationships.
A datasource file called ejbrelationships-ds.xml
is created, and saved in the jboss/server/default/deploy folder
<datasources>
<local-tx-datasource>
<jndi-name>EJBRelationshipsDS</jndi-name>
<connection-url>jdbc:mysql://192.168.0.8:3306/ejbrelationships</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>user</user-name>
<password>password</password>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
The project has a file called persistence.xml, which
is used by the EJB's to connect to the datasource.
<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="EJBRelationshipsPU">
<jta-data-source>java:/EJBRelationshipsDS</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto"
value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
The property hibernate.hbm2ddl.auto has
a value of create-drop.
This informs JBoss to create the database tables on deployment,
and to drop them, when the application is undeployed.
The web application deployment descriptor, web.xml
is shown below.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>EJBRelationships</servlet-name>
<servlet-class>com.j3ltd.web.RelationshipServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>EJBRelationships</servlet-name>
<url-pattern>/relationships</url-pattern>
</servlet-mapping>
</web-app>
When the url http://localhost:8080/EJBRelationships/relationships
is opened in a browser, the following output is received:
Create test data...done
List of Person ejbs:
Person retrieved: Mr Blair
role: pm pres
Home: 10 Downing Street
Person retrieved: Mr Brown
role: vp
Home: 10 Downing Street
Home: 11 Downing Street
Person retrieved: Mr Bush
role: pres
Home: Whitehouse, Washington DC
Person retrieved: Mr Cheney
role: vp
Home: Whitehouse, Washington DC
Person retrieved: Mr Chirac
role: pres
Home: Élisée Palace
List of Home ejbs:
Home retrieved: 10 Downing Street
Person: Mr Blair
role: pm pres
Person: Mr Brown
role: vp
Home retrieved: 11 Downing Street
Person: Mr Brown
role: vp
Home retrieved: Whitehouse, Washington DC
Person: Mr Bush
role: pres
Person: Mr Cheney
role: vp
Home retrieved: Élisée Palace
Person: Mr Chirac
role: pres
Delete some data...done
List of Person ejbs:
Person retrieved: Mr Brown
role: vp
Home: 10 Downing Street
Person retrieved: Mr Bush
role: pres
Home: Whitehouse, Washington DC
Person retrieved: Mr Cheney
role: vp
Home: Whitehouse, Washington DC
List of Home ejbs:
Home retrieved: 10 Downing Street
Person: Mr Brown
role: vp
Home retrieved: Whitehouse, Washington DC
Person: Mr Bush
role: pres
Person: Mr Cheney
role: vp
Home retrieved: Élisée Palace
|