JPA 2.0 Mapping Many to Many

As with many things there is often more than one way to accomplish a task in code.  The example below demonstrates a simple example of how to map a many to many relationship using JPA 2.0, followed by a slightly more complex but more flexible way to do the same thing..

First things first though. It is important to understand how relationships work in JPA. The direction of the relationship is very important. When you annotate a property within a Java Class with @ManyToOne you are stating that the class object’s table can have multiple entries for a single object property you are placing the annotation on within the class. However since your putting the annotation on the property, you might think it would work the other way around. Stop! Soak that in! I will try to provide examples below.

For this example we will create a bi-directional relationship between a system user and an organization. This means that a user may belong to any number of organizations, and organizations contain many users. It requires an association table (join table) to keep track of the relationships.

On the database side the tables would look something like this :

USER
USER_ID USERNAME JOB_TITLE
12345 JoeBob Guru
ORGANIZATION
ORGANIZATION_ID ADDRESS NAME
56789 somewhere in the octagon Initech

And this association / join table to keep track.   (Typical Convention is “OwningTableName” _ “InverseEntityTableName”), but we will show the relationship using the JoinTable annotation so any table name will suffice.  We will also specify the column names in the join table such that default/standardized naming will not be required.

* For example, if default column names are used in the join table and the default table name is correct it can be as simple as adding the annotation @ManyToMany on the owning side (USER in this case) and @ManyToMany(mappedBy = “users”) on the inverse side (ORGANIZATION in this case).

USER_ORGANIZATION
USER_ID ORGANIZATION_ID
12345 56789
@Entity
@Table(name="ORGANIZATION", schema = "MY_SCHEMA")
public class Organization implements Serializable{

	@Id
	@Column(name = "ORGANIZATION_ID")
	private String organizationId;

	// keeping the example simple we wont create an address object
	@Column (name = "ADDRESS")
	private String address;

	@Column (name = "ORG_NAME")
	private String name;

	//bi-directional many-to-many association to USER
	@ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
	@JoinTable(name="USER_ORGANIZATION", schema = "TABLE_MASTER",
		joinColumns = {@JoinColumn(name="ORGANIZATION_ID")},
		inverseJoinColumns = {@JoinColumn(name="USER_ID")}
	)
	private Set users;

	public String getOrganizationId() {
		return organizationId;
	}

	public void setOrganizationId(String organizationId) {
		this.organizationId = organizationId;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Set getUsers() {
		return users;
	}

	public void setUsers(Set users) {
		this.users = users;
	}

}
package jpa;

@Entity
@Table(name="USER", schema = "MY_SCHEMA")
public class User implements Serializable{

	@Column(name="USER_ID")
	@Id
	private String userId;

	@Column(name="USERNAME")
	private String username;

	@Column (name="JOB_TITLE")
	private String jobTitle;

	// LAZY tells the class to only load the list if it is acessed within the transaction.
	// We also want the elements in the association table to be removed if the user is deleted.
	@ManyToMany (cascade=CascadeType.ALL, fetch = FetchType.LAZY)
	@JoinTable(name ="USER_ORGANIZATION", schema = "MY_SCHEMA",
			joinColumns = {@JoinColumn(name="USER_ID")},
			inverseJoinColumns = {@JoinColumn(name="ORGANIZATION_ID")}
	)
	private Set organizations;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getJobTitle() {
		return jobTitle;
	}

	public void setJobTitle(String jobTitle) {
		this.jobTitle = jobTitle;
	}

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

	public Set getOrganizations() {
		return organizations;
	}

	public void setOrganizations(Set organizations) {
		this.organizations = organizations;
	}

}

And life is good! We don’t even need to really understand the direction of the relationship since we annotated @ManyToMany. At least for a moment anyways. But then your clever coding buddy tells you you should make this system more robust and there is a problem with what you have done. Here you thought you had solved everything on the first try and were happy using @ManyToMany because it made life so simple.

Suppose instead that Joe Bob is a man of many talents. What happens if he holds different job titles for different companies at the same time? The job title property would then need to be owned by the relationship table / entity bean rather than stored under the user object. Our mapping above will not do at all! This new relationship doesn’t seem all that more complex, but how can we fix this since we have no entity object for the association table? The new tables would look like this:

USER
USER_ID USERNAME
12345 JoeBob
ORGANIZATION
ORGANIZATION_ID ADDRESS NAME
56789 Somewhere in the octagon Initech
efgh Northern California Russian River

And this association / join table to keep track.

USER_ORGANIZATION
USER_ID ORGANIZATION_ID JOB_TITLE
12345 56789 Java Guru
12345 efgh MASTER_BREWER

So instead we need to create an entity for the association table, and an entity that will map to the composite key which identifies the properties that will make up the primary key in the association table.

@Embeddable
public class OrganizationUserPk implements Serializable {

	private static final long serialVersionUID = 1L;

	private String aUserId;

	private String anOrganizationId;

	public OrganizationUserPk(){}

    /* Constructor should create the composite primary key */
	public OrganizationUserPk(String userId, String organizationId){
		this.aUserId = userId;
		this.anOrganizationId = organizationId;
	}

	public String getaUserId() {
		return aUserId;
	}

	public void setaUserId(String aUserId) {
		this.aUserId = aUserId;
	}

	public String getAnOrganizationId() {
		return anOrganizationId;
	}

	public void setAnOrganizationId(String anOrganizationId) {
		this.anOrganizationId = anOrganizationId;
	}

}
/**
 * This entity is created to represent the association table relationship between the user and the organization
 * @author Javasavy
 *
 */
@Entity
@Table(name="ORGANIZATION_USER", schema = "MY_SCHEMA")
public class OrganizationUser implements Serializable {

    private static final long serialVersionUID = 1L;

	@EmbeddedId
	private OrganizationUserPk organizationUserPk;

	@ManyToOne
	@JoinColumn(name = "USER_ID", nullable = false, insertable = true, updatable = true)
	// Although I might use "userId" for @MapsId in the real world I want to be clear the that mapsId refers to the
	// property in the OrganizationUserPk object, not the userId in the User.java class.  JPA will automagically look for
	// and use the @Id annotated property in the User.java class when resolving this mapping.
	@MapsId("aUserId")
	private User user;

	@ManyToOne
	@JoinColumn(name = "ORGANIZATION_ID", nullable = false, insertable = true, updatable = true)
	@MapsId("anOrganizationId")
	private Organization organization;

	@Column (name="JOB_TITLE")
	private String jobTitle;

	/* Constructor */
	public OrganizationUser (User user, Organization organization, String jobTitle){
		this.user= user;
		this.organization = organization;
		this.jobTitle = jobTitle;
	}

	public OrganizationUserPk getOrganizationUserPk() {
		return organizationUserPk;
	}

	public void setOrganizationUserPk(OrganizationUserPk organizationUserPk) {
		this.organizationUserPk = organizationUserPk;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public Organization getOrganization() {
		return organization;
	}

	public void setOrganization(Organization organization) {
		this.organization = organization;
	}

	public String getJobTitle() {
		return jobTitle;
	}

	public void setJobTitle(String jobTitle) {
		this.jobTitle = jobTitle;
	}
}
@Entity
@Table(name="USER", schema = "MY_SCHEMA")
public class User implements Serializable{

    private static final long serialVersionUID = 1L;

	@Id
	@Column(name="USER_ID")
	private String userId;

	@Column(name="USERNAME")
	private String username;

	// mappedBy refers to the "user" member variable within OrganizationUser.java class, however remember that we are Stating
	// that there can only be multiple OrganizationUser entries for a single user by marking it as @OneToMany
	// (One table row in USER, can have multiple rows in the table mapped my OrganizationUser: ORGANIZATION_USER
	// we set orphanRemoval to true so that if the User is deleted all orphans in the ORGANIZATION_USER table
	// will also be removed.
	@OneToMany(mappedBy = "user", cascade=CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval=true)
	private Set organizationUsers;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

	public Set getOrganizationUsers() {
		return organizationUsers;
	}

	public void setOrganizationUsers(Set organizationUsers) {
		this.organizationUsers = organizationUsers;
	}
}
@Entity
@Table(name="ORGANIZATION", schema = "MY_SCHEMA")
public class Organization implements Serializable{

    private static final long serialVersionUID = 1L;

	@Id
	@Column(name = "ORGANIZATION_ID")
	private String organizationId;

	// location keeping the example simple we wont create an address object
	@Column (name = "ADDRESS")
	private String address;

	@Column (name = "ORG_NAME")
	private String name;

	@OneToMany(mappedBy = "organization", cascade=CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval=true)
	private Set organizationUsers;

	public String getOrganizationId() {
		return organizationId;
	}

	public void setOrganizationId(String organizationId) {
		this.organizationId = organizationId;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Set getOrganizationUsers() {
		return organizationUsers;
	}

	public void setOrganizationUsers(Set organizationUsers) {
		this.organizationUsers = organizationUsers;
	}
}

Life is good once again, and Joe Bob gets to continue on with his life as a Java Guru and Master Brewer!
One thing to note is that the above configuration does NOT support having two different jobs / job titles at the same organization! If we wanted to do that we would probably want break out the job title description into a seperate table with an appropriate primary key, then use this new primary key in the association table in place of the job_title description. It would also require new mappings in the entities. The Composite Pk object would then consist of userId, OrganizationId, and jobTitleId.




No Comments


You can leave the first : )



Leave a Reply

Your email address will not be published. Required fields are marked *