I have made a usertype to map a VARCHAR column to a Calendar object, and it's working perfectly using the following mapping definition:
Code:
<property name="activeFrom">
<column name="ActiveFrom"/>
<type name="no.bekk.mobiletimekeeper.customtypes.CalendarStringType"/>
</property>
But when I try to use this usertype as part of a composite primary key like this:
Code:
<composite-id>
<key-many-to-one name="project" class="Project" column="ProjectID"/>
<key-many-to-one name="employee" class="Employee" column="EmployeeID"/>
<key-property name="date"
column="ActualDate"
type="no.bekk.mobiletimekeeper.CalendarStringType">
</key-property>
</composite-id>
I get the following exception:
Quote:
org.hibernate.MappingException: Could not determine type for: no.bekk.mobiletimekeeper.CalendarStringType, for columns: [org.hibernate.mapping.Column(ActualDate)]
org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:265)
org.hibernate.mapping.Property.getType(Property.java:47)
org.hibernate.mapping.Component.getType(Component.java:149)
org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:252)
org.hibernate.mapping.RootClass.validate(RootClass.java:189)
org.hibernate.cfg.Configuration.validate(Configuration.java:816)
org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1050)
no.bekk.mobiletimekeeper.DatabaseConnection.currentSession(DatabaseConnection.java:38)
no.bekk.mobiletimekeeper.DatabaseConnection.getSortedList(DatabaseConnection.java:58)
no.bekk.mobiletimekeeper.Employees.<init>(Employees.java:26)
no.bekk.mobiletimekeeper.MtsServlet.doGet(MtsServlet.java:44)
javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
My usertype CalendarStringType looks like this:
Code:
public class CalendarStringType implements UserType
{
private static final int[] SQL_TYPES = {Types.VARCHAR};
public CalendarStringType() {}
public int[] sqlTypes()
{
return SQL_TYPES;
}
/**
* Tells Hibernate what Java type is mapped by this UserType.
* @return The Java type which is mapped by this UserType
*/
public Class returnedClass()
{
return GregorianCalendar.class;
}
/**
* The UserType is responsible for dirty-checking property values. The equals()
* method compares the current property value to a previous snapshot and determines
* whether the property is dirty and must by saved to the database.
*/
public boolean equals( Object x, Object y ) throws HibernateException
{
if( x != null && y != null)
return ((GregorianCalendar)x).equals( (GregorianCalendar)y );
return false;
}
/**
* @return the objects hashcode
*/
public int hashCode( Object x ) throws HibernateException
{
return ((GregorianCalendar)x).hashCode();
}
/**
* retrieves the property value from the JDBC ResultSet.
*/
public Object nullSafeGet( ResultSet resultSet, String[] names, Object owner )
throws HibernateException, SQLException
{
if (resultSet.wasNull())
return null;
//The true/false value is stored in the database as tinyint.
String dateString = resultSet.getString( names[0] );
//returning a new Calendar object whoose value is set according to the value
//of the String date. In short: parsing the retrieved string and makes a new
//Calendar objects
return parseDateString( dateString );
}
/**
* writes the property value to the JDBC PreparedStatement
*/
public void nullSafeSet( PreparedStatement prepStatement, Object value, int index )
throws HibernateException, SQLException
{
if (value == null)
{
prepStatement.setNull( index, Types.VARCHAR );
}
else
{
String year = ((GregorianCalendar)value).get( Calendar.YEAR ) + "";
String month = Toolbox.twoDigits( ((GregorianCalendar)value).get( Calendar.MONTH ) );
String date = Toolbox.twoDigits( ((GregorianCalendar)value).get( Calendar.DATE ) );
//perform a conversion to get Hibernate to store an appropriate VARCHAR value
//from the String object.
prepStatement.setString( index, year + month + date );
}
}
/* (non-Javadoc)
* @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object)
*/
public Object deepCopy( Object value ) throws HibernateException
{
//extract the date parameters
int year = ((GregorianCalendar)value).get( Calendar.YEAR );
int month = ((GregorianCalendar)value).get( Calendar.MONTH );
int date = ((GregorianCalendar)value).get( Calendar.DATE );
return new GregorianCalendar( year, month, date );
}
/**
* The Java type Calendar is mutable (the type's contents can be changed after instanciation),
* hence this method will return true.
* @return will always return true.
*/
public boolean isMutable()
{
return true;
}
/* (non-Javadoc)
* @see org.hibernate.usertype.UserType#disassemble(java.lang.Object)
*/
public Serializable disassemble( Object value ) throws HibernateException
{
return null;
}
/* (non-Javadoc)
* @see org.hibernate.usertype.UserType#assemble(java.io.Serializable, java.lang.Object)
*/
public Object assemble( Serializable cached, Object owner )
throws HibernateException
{
return null;
}
/* (non-Javadoc)
* @see org.hibernate.usertype.UserType#replace(java.lang.Object, java.lang.Object, java.lang.Object)
*/
public Object replace( Object original, Object target, Object owner )
throws HibernateException
{
return null;
}
/**
* A helper method to do the parsing from a YYYYMMDD-formatted String
* to a Java Calendar object.
* @param toBeParsed The String to parse. It must be in the format YYYYMMDD
* @param calendar the Calendar object that shall recieve the date parameters from the parsed
* String
* @throws NumberFormatException If the String toBeParsed isn't correctly formatted, this
* exception is thrown.
*/
private Calendar parseDateString( String toBeParsed ) throws NumberFormatException
{
//instanciate a new Calendar object
GregorianCalendar calendar = new GregorianCalendar();
if( toBeParsed.length() == 8 )
{
//extract the year, month and day from the recieved string
int year = Integer.parseInt( toBeParsed.substring(0,4) );
int month = Integer.parseInt( toBeParsed.substring(4,6) ) - 1; //subtract 1, months are from 0 to 11
int day = Integer.parseInt( toBeParsed.substring(6,8) );
//store the date
calendar.set(year, month, day);
return calendar;
}
else
{
throw new NumberFormatException("The date string must have the exact " +
"length of 8. It must also only contain digits.\nDebug info:\n" +
"String toBeParsed = " + toBeParsed );
}
}
}
I hope I have provided enough information for anyone to shed some light on what I should investigate on, but please ask for anything missing required to perhaps solve this problem. I'm using MySQL and Hibernate 3.0.3.