Java – Help with Hibernate Collection Foreign Key Mapping

hibernatejavaMySQL

I'm running into significant difficulty getting my mapping file to work with a collection of elements via a foreign key in Hibernate. Java will try to load these files below and won't have any runtime exceptions, but the events table is never loaded as an object of an employee (it will remain null, but every other attribute loads from the DB). In my MySQL database, I have the following:

TABLE: Employees

Primary Key: username varchar

name varchar

areacode INTEGER

phone INTEGER

addressNumber INTEGER

addressLocation varchar

email varchar

TABLE: Events

Primary Key: ID INTEGER

startDate DateTime

endDate DateTime

customerName varchar

employeeUsername varchar

<?xml version="1.0"?>

<class name="Employee" table="Employees">
    <id name="username" column="username" type="string"/>
    <many-to-one name="contact" class="Contact" column="username" unique="false" update="false" insert="false" optimistic-lock="true" not-found="exception" embed-xml="true" />
</class>

<class name="Contact" table="Employees">
    <id name="username" column="username" type="string"/>
    <property name="name" column="name" type="string"/>
    <property name="addressNumber" column="addressNumber" type="int"/>
    <property name="areacode" column="areacode" type="int"/>
    <property name="phone" column="phone" type="int"/>
    <property name="addressLocation" column="addressLocation" type="string"/>
    <property name="email" column="email" type="string"/>
</class>

<?xml version="1.0"?>

<class name="Event" table="Events">
    <id name="ID" column="ID" type="int">
        <generator class="assigned"/>
    </id>
    <property name="startDate" column="startDate" type="date"/>
    <property name="endDate" column="endDate" type="date"/>
    <property name="customerName" column="customerName" type="string"/>
</class>

<class name="Employee" table="Events" entity-name="Employee2">
    <id name="username" column="employeeUsername" type="string"/>
    <list name="events" cascade="all">
        <key column="employeeUsername"/>
        <list-index column="ID"/>
        <one-to-many class="Event"/>
    </list>
</class>

Assume the hibernate.cfg.xml file exists and works (If you feel this needs to be shown, please let me know). It does include both of the files in its mapping files part.

I have a feeling my error is probably in my declaration of the collection of events in my "list" declaration. Here are the Java classes:

package org.hibernate.employee;

import java.util.Date;

public class Event {
private int ID;
private Date startDate;
private Date endDate;
private String customerName;

public Event(){
    System.out.println("blah");
}

/**
 * @return the iD
 */
public int getID() {
    return ID;
}
/**
 * @param id the iD to set
 */
public void setID(int id) {
    ID = id;
}
/**
 * @return the startDate
 */
public Date getStartDate() {
    return startDate;
}
/**
 * @param startDate the startDate to set
 */
public void setStartDate(Date startDate) {
    this.startDate = startDate;
}
/**
 * @return the endDate
 */
public Date getEndDate() {
    return endDate;
}
/**
 * @param endDate the endDate to set
 */
public void setEndDate(Date endDate) {
    this.endDate = endDate;
}
/**
 * @return the customerName
 */
public String getCustomerName() {
    return customerName;
}
/**
 * @param customerName the customerName to set
 */
public void setCustomerName(String customerName) {
    this.customerName = customerName;
}

}

/*

* File: Contact.java
*/

package org.hibernate.employee;

/**
* This class represents a Contact information for an Employee.
*
*/
public class Contact {

private int username; // the contact identifier in the database
private int areacode; // the contact areacode
private int phone; // the contact phone
private String name; // the contact's name
private int addressNumber; // the contact's address number
private String addressLocation; // the contact's address location
private String email; // the contact's email

/**
 * Constructs a Contact object.
 */
public Contact() {

}

/**
 * @return the areacode
 */
public int getAreacode() {
    return areacode;
}

/**
 * @param areacode the areacode to set
 */
public void setAreacode(int areacode) {
    this.areacode = areacode;
}

/**
 * @return the phone
 */
public int getPhone() {
    return phone;
}

/**
 * @param phone the phone to set
 */
public void setPhone(int phone) {
    this.phone = phone;
}

/**
 * @return the name
 */
public String getName() {
    return name;
}

/**
 * @param name the name to set
 */
public void setName(String name) {
    this.name = name;
}

/**
 * @return the addressNumber
 */
public int getAddressNumber() {
    return addressNumber;
}

/**
 * @param addressNumber the addressNumber to set
 */
public void setAddressNumber(int addressNumber) {
    this.addressNumber = addressNumber;
}

/**
 * @return the addressLocation
 */
public String getAddressLocation() {
    return addressLocation;
}

/**
 * @param addressLocation the addressLocation to set
 */
public void setAddressLocation(String addressLocation) {
    this.addressLocation = addressLocation;
}

/**
 * @return the email
 */
public String getEmail() {
    return email;
}

/**
 * @param email the email to set
 */
public void setEmail(String email) {
    this.email = email;
}

public String toString(){
    String retVal = "";
    retVal += "Address: " + this.addressNumber + " " + this.addressLocation + "\n";
    retVal += "Email: " + this.email + "\n";
    retVal += "Phone: " + this.areacode + " " + this.phone + "\n";
    retVal += "Name: " + this.name + "\n";
    return retVal;
}

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

public int getUsername() {
    return username;
}

}

/*

* File: Employee.java
*/

package org.hibernate.employee;
import java.util.List;

/**
* This class represents an Employee in a company database.
*
*/
public class Employee {

private String username; // the employee's username
private Contact contact; // the employee's contact information
private List events;

/**
 * Constructs an Employee object.
 */
public Employee() {
    super();
}

/**
 * @return the username
 */
public String getUsername() {
    return username;
}

/**
 * @param username the username to set
 */
public void setUsername(String username) {
    this.username = username;
}

/**
 * @return the contact
 */
public Contact getContact() {
    return contact;
}

/**
 * @param contact the contact to set
 */
public void setContact(Contact contact) {
    this.contact = contact;
}

/**
 * @return the events
 */
public List getEvents() {
    return events;
}

/**
 * @param events the events to set
 */
public void setEvents(List events) {
    this.events = events;
}

public String toString(){
    String retVal = "";
    System.out.println(events);
    retVal += "Username: " + username + "\n";
    retVal += "Contact: " + contact + "\n";
    return retVal;
}

}

Best Answer

Your mappings are wrong for two (main) reasons:

  1. Mapping both Employee and Contact to the same table that way is not going to work. many-to-one requires a foreign key (e.g. a separate column); you're trying to reuse the primary key instead.
  2. Re-mapping Employee class to a different table isn't going to work either - table and class are incompatible. Using a different entity name prevents an immediate error, but that's not the appropriate usage of it.

What you should do instead is:

  1. Map Contact as component.
  2. Map collection of events directly on Employee as one-to-many association.

Something like:

<class name="Employee" table="Employees">
  <id name="username" column="username" type="string"/>

  <component name="Contact" class="Contact"> <!-- class attribute optional -->
    <property name="name" column="name" type="string"/>
    <property name="addressNumber" column="addressNumber" type="int"/>
    <property name="areacode" column="areacode" type="int"/>
    <property name="phone" column="phone" type="int"/>
    <property name="addressLocation" column="addressLocation" type="string"/>
    <property name="email" column="email" type="string"/>
  </component>
  <list name="events" cascade="all">
    <key column="employeeUsername"/>
    <list-index column="event_idx"/>
    <one-to-many class="Event"/>
  </list>  
</class>

Note that list-index should really be a separate column in your table if you want to map list of events as ordered list; you can't map it to id. If you don't want to define it as a column, you can either map the list as <bag> instead (and, optionally, specify order-by attribute to sort that bag by) or map it as collection of composite elements where each Event will no longer be an independent entity. Whether or not that's applicable depends on your business requirements.

Related Topic