I am trying to implement a many-to-one foreign key relationship between two tables using Hibernate 3.3.0RC1. There are two tables, Country and Location, where the former is a reference list of country names and ISO 3166 codes and the latter contains details about sites in our application. The Location to Country relationship is that which should be many-to-one and I am attempting to use the field countryCode in Location as a foreign key to the countryCode field of Country. This field is a java.lang.String.
1. If the foreign key is based on a non-key attribute and property-ref="countryCode" it attempts to call a getCountryCode() method on a java.lang.String.
2. If the foreign key is based on a a key of the Country table, with no property-ref, it attempts to make a call to the getId() method of a Country class, but the target is clearly a java.lang.String - specifically the string representing the ISO code.
3. If I add class="com.example.odm.beans.Country" to the many-to-one it also attempts to call a Country.getId() method with the same results as 2.
Here is an anonymised version of my Country.hbm.xml:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.example.odm.beans.Country" table="country">
<id name="id">
<column name="id" not-null="true"/>
<generator class="native"/>
</id>
<property name="countryCode" not-null="true" unique="true"/>
<property name="name"/>
</class>
</hibernate-mapping>
The corresponding database table is:
Code:
ID NOT NULL NUMBER(10)
COUNTRYCODE NOT NULL VARCHAR2(255 CHAR)
NAME VARCHAR2(255 CHAR)
The source file for the Country class is:
Code:
package com.example.odm.beans;
import java.io.Serializable;
import org.apache.commons.lang.builder.HashCodeBuilder;
import com.example.util.faces.SelectItemMappable;
import com.example.persist.IdAware;
public class Country implements Serializable, SelectItemMappable, IdAware
{
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer id=-1;
public Integer getId(){
return this.id;
}
public void setId(Integer id){
this.id=id;
}
public static long getSerialVersionUID()
{
return serialVersionUID;
}
public Country()
{}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getCountryCode()
{
return countryCode;
}
public void setCountryCode(String iso_3166_code)
{
this.countryCode = iso_3166_code;
}
private String countryCode, name;
public int hashCode()
{
return new HashCodeBuilder().append(name).hashCode();
}
@Override
public String getSelectItemLabel()
{
return this.getName();
}
@Override
public Object getSelectItemValue()
{
return this.getId();
}
}
The .hbm file for Locations is:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<typedef name="LocationType" class="com.example.common.util.hibernate.GenericEnumUserType">
<param name="enumClass">com.example.odm.beans.LocationType</param>
</typedef>
<class name="com.example.odm.beans.Location" table="locations" lazy="false">
<id name="id">
<column name="id" not-null="true"/>
<generator class="native"/>
</id>
<property name="OID" not-null="true" unique="true"/>
<property name="name" not-null="true"/>
<property name="protocol"/>
<property name="address"/>
<property name="maxUsers"/>
<property name="active"/>
<property name="locationNumber"/>
<property name="locationType" column="location_type" type="LocationType" not-null="true"/>
<bag name="metaDataVersionRefs" table="locations_metadata_versions" lazy="true">
<key column="location_id"/>
<composite-element class="com.example.odm.beans.MetaDataVersionRef">
<many-to-one name="study" class="com.example.odm.beans.Study" not-null="true"/>
<many-to-one name="metaDataVersion" class="com.example.odm.beans.MetaDataVersion" not-null="true"/>
<property name="effectiveDate" column="effective_date" not-null="true"/>
</composite-element>
</bag>
<many-to-one name="countryCode" class="com.example.odm.beans.Country" column="COUNTRYCODE" not-null="true"/>
<bag name="assignedUsers" table="users_locations" cascade="all" lazy="false">
<key column="location_id"/>
<composite-element class="com.example.coretailor.beans.AssignedUser">
<many-to-one name="referent" class="com.example.odm.beans.User" cascade="all" column="user_id" not-null="true" lazy="proxy"/>
</composite-element>
</bag>
<!-- FIXME(retnuh): need country, but needs more thought -->
<property name="lastModifiedDate" type="timestamp"/>
<property name="lastModifiedByIpAddress"/>
</class>
<query name="findLocations">
select d from Location d order by d.locationNumber asc
</query>
<query name="findNextLocationNumber">
select MAX(d.locationNumber) from Location d
</query>
</hibernate-mapping>
The class is as follows:
Code:
package com.example.odm.beans;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.example.common.beans.Role;
import com.example.application.beans.AssignedUser;
import com.example.util.faces.SelectItemMappable;
public class Location extends AbstractODMBean implements SelectItemMappable, Comparable<Location>
{
private static final long serialVersionUID = 3891766030120879400L;
private static final Log log = LogFactory.getLog(User.class);
public Location()
{}
public String getOID()
{
return oid;
}
public void setOID(String oid)
{
this.oid = oid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public LocationType getLocationType()
{
return locationType;
}
public void setLocationType(LocationType locationType)
{
this.locationType = locationType;
}
public String getProtocol()
{
return protocol;
}
public void setProtocol(String protocol)
{
this.protocol = protocol;
}
public String getAddress()
{
return address;
}
public void setAddress(String address)
{
this.address = address;
}
public Integer getMaxUsers()
{
return maxUsers;
}
public void setMaxUsers(Integer maxUsers)
{
this.maxUsers = maxUsers;
}
public Boolean isActive()
{
return active;
}
public void setActive(Boolean active)
{
this.active = active;
}
public Timestamp getLastModifiedDate()
{
return lastModifiedDate;
}
public void setLastModifiedDate(Timestamp lastModifiedDate)
{
this.lastModifiedDate = lastModifiedDate;
}
public String getLastModifiedByIpAddress()
{
return lastModifiedByIpAddress;
}
public void setLastModifiedByIpAddress(String lastModifiedByIpAddress)
{
this.lastModifiedByIpAddress = lastModifiedByIpAddress;
}
public List<MetaDataVersionRef> getMetaDataVersionRefs()
{
return metaDataVersionRefs;
}
public void setMetaDataVersionRefs(List<MetaDataVersionRef> metaDataVersionRefs)
{
this.metaDataVersionRefs = metaDataVersionRefs;
}
public void addMetaDataVersionRef(MetaDataVersionRef metaDataVersionRef)
{
metaDataVersionRefs.add(metaDataVersionRef);
}
public Integer getLocationNumber()
{
return this.locationNumber;
}
public void setLocationNumber(Integer locationNumber)
{
this.locationNumber = locationNumber;
}
public boolean equals(Object o)
{
if (o instanceof Location)
return new EqualsBuilder().append(oid, ((Location)o).oid).isEquals();
return false;
}
public int hashCode()
{
return new HashCodeBuilder().append(oid).hashCode();
}
public String toString()
{
return "[Site " + name + " oid=" + oid + "]";
}
@Override
public String getSelectItemLabel()
{
return getName();
}
@Override
public Object getSelectItemValue()
{
return getId();
}
public Boolean getActive()
{
return active;
}
public void setSubjectCount(long subjectCount)
{
this.subjectCount = subjectCount;
}
public long getSubjectCount()
{
return subjectCount;
}
public List<AssignedUser> getAssignedUsers()
{
return assignedUsers;
}
public void setAssignedUsers(List<AssignedUser> assignedUsers)
{
this.assignedUsers = assignedUsers;
}
public void addAssignedUser(User user)
{
assignedUsers.add(new AssignedUser(user));
}
public List<User> myCRAs()
{
List<User> cras = retrieveUsers(Role.CRA);
return cras;
}
public String myCRANames()
{
String names = "";
for (User user: retrieveUsers(Role.CRA)) {
if (names.compareTo("") != 0)
names = names + ", ";
names = names + user.getLoginName();
}
return names;
}
public User myInvestigator()
{
List<User> investigators = retrieveUsers(Role.INVESTIG);
if (investigators.size() == 1)
return investigators.get(0);
else if (investigators.size() == 0) {
log.warn("No investigator assigned for Location: " + this.getName());
return null;
} else {
log.warn("More then one investigators assigned for Location: " + this.getName());
return investigators.get(0);
}
}
public User myPrincipalCRC()
{
List<User> princCRC = retrieveUsers(Role.PRINCRC);
if (princCRC.size() == 1)
return princCRC.get(0);
else if (princCRC.size() == 0) {
log.warn("No PrincCRC assigned for Location: " + this.getName());
return null;
} else {
log.warn("More then one PrincCRC assigned for Location: " + this.getName());
return princCRC.get(0);
}
}
public List<User> retrieveUsers(Role role)
{
List<User> users = new ArrayList<User>();
for (AssignedUser assignedUser: assignedUsers) {
User user = assignedUser.getReferent();
if (role == null || user.getRole() == role)
users.add(user);
}
return users;
}
@Override
public int compareTo(Location o) {
return this.locationNumber - o.getLocationNumber();
}
public void setCountryCode(String countryCode){
this.countryCode=countryCode;
}
public String getCountryCode(){
return countryCode;
}
private String protocol;
private String oid, name, address,countryCode;
private LocationType locationType = LocationType.Site;
private List<MetaDataVersionRef> metaDataVersionRefs = new ArrayList<MetaDataVersionRef>();
private Integer maxUsers;
private long subjectCount;
private Boolean active = Boolean.TRUE;
private Integer locationNumber;
private Timestamp lastModifiedDate;
private String lastModifiedByIpAddress;
private List<AssignedUser> assignedUsers = new ArrayList<AssignedUser>();
}
The corresponding database table is:
Code:
ID NOT NULL NUMBER(10)
OID NOT NULL VARCHAR2(255 CHAR)
PROTOCOL NOT NULL VARCHAR2(255 CHAR)
ADDRESS VARCHAR2(255 CHAR)
MAXUSERS NUMBER(10)
ACTIVE NUMBER(1)
LOCATIONNUMBER NUMBER(10)
LOCATION_TYPE NOT NULL VARCHAR2(255 CHAR)
COUNTRYCODE NOT NULL VARCHAR2(255 CHAR)
LASTMODIFIEDDATE TIMESTAMP(6)
LASTMODIFIEDBYIPADDRESS VARCHAR2(255 CHAR)
The country codes for locations are read in from an XML file, but when the call to a Hibernate save or saveOrUpdate is made the IllegalArgumentException occurs in the two cases above. How can this be solved - is it dependent on which version of Hibernate is used?