Friday, May 11, 2007

Hibernate & enumerations (Part II)

Yesterday I faced the problem of mapping enumerations to bd using hbm.xml files. Now I will create a simple example to show you the solution for that problem.

Lets create a simple domain class :

public final class User {

/** The unique identifier. Must be unique among all service events. */
private String id;

/** The name of the particular user. */
private String name;

/** The education of the user. */
private Education educationType;

// here comes constructor and appropriate getters and setters.

}


Next we have to create our enumeration called Education.
For instance :

public enum Education{
/** */
UNIVERSITY,

/** */
COLLEGE,

/** */
SCHOOL
}


A now comes the trick. With help of my colleagues I found class created by Emmanuel Bernard which is the enum type wrapper. Here it is :

public class EnumType> implements UserType {
/** */
private static Log log = LogFactory.getLog(EnumType.class);

/** */
private static final boolean IS_TRACE_ENABLED;

static {
//cache this, because it was a significant performance cost
IS_TRACE_ENABLED = LogFactory.getLog(StringHelper.qualifier(Type.class.getName())).isTraceEnabled();
}

public static final String ENUM = "enumClass";
public static final String SCHEMA = "schema";
public static final String CATALOG = "catalog";
public static final String TABLE = "table";
public static final String COLUMN = "column";
public static final String TYPE = "type";

private static Map
enumValues = new HashMap();

private Class
enumClass;
// private int sqlType = Types.INTEGER; // enums will be mapped in bd as integer.
private int sqlType = Types.VARCHAR; // enums will be mapped in bd as varchar.

protected EnumType(final Class
c) {
this.enumClass = c;
}

public int[] sqlTypes() {
return new int[]{sqlType};
}

public Class returnedClass() {
return enumClass;
}

public boolean equals(final Object x, final Object y) throws HibernateException {
return x == y;
}

public int hashCode(final Object x) throws HibernateException {
return x == null ? 0 : x.hashCode();
}

public Object nullSafeGet(final ResultSet rs, final String[] names, final Object owner) throws HibernateException, SQLException {
final Object object = rs.getObject(names[0]);
if (rs.wasNull()) {
if (IS_TRACE_ENABLED) {
log.debug("Returning null as column " + names[0]);
}
return null;
}
if (object instanceof Number) {
Object[] values = enumValues.get(enumClass);
if (values == null) {
try {
final Method method = enumClass.getDeclaredMethod("values", new Class[0]);
values = (Object[]) method.invoke(null, new Object[0]);
enumValues.put(enumClass, values);
}
catch (Exception e) {
throw new HibernateException("Error while accessing enum.values(): " + enumClass, e);
}
}
final int ordinal = ((Number) object).intValue();
if (ordinal <>= values.length) {
throw new IllegalArgumentException("Unknown ordinal value for enum " + enumClass + ": " + ordinal);
}
if (IS_TRACE_ENABLED) {
log.debug("Returning '" + ordinal + "' as column " + names[0]);
}
return values[ordinal];
} else {
final String name = (String) object;
if (IS_TRACE_ENABLED) {
log.debug("Returning '" + name + "' as column " + names[0]);
}
try {
return Enum.valueOf(enumClass, name);
}
catch (IllegalArgumentException iae) {
throw new IllegalArgumentException("Unknown name value for enum " + enumClass + ": " + name, iae);
}
}
}

public void nullSafeSet(final PreparedStatement st, final Object value, final int index) throws HibernateException, SQLException {
//if (!guessed) guessType( st, index );
if (value == null) {
if (IS_TRACE_ENABLED) log.debug("Binding null to parameter: " + index);
st.setNull(index, sqlType);
} else {
final boolean isOrdinal = isOrdinal(sqlType);
if (isOrdinal) {
final int ordinal = ((Enum) value).ordinal();
if (IS_TRACE_ENABLED) {
log.debug("Binding '" + ordinal + "' to parameter: " + index);
}
st.setObject(index, new Integer(ordinal), sqlType);
} else {
final String enumString = ((Enum) value).name();
if (IS_TRACE_ENABLED) {
log.debug("Binding '" + enumString + "' to parameter: " + index);
}
st.setObject(index, enumString, sqlType);
}
}
}

private boolean isOrdinal(final int paramType) {
switch (paramType) {
case Types.INTEGER:
case Types.NUMERIC:
case Types.SMALLINT:
case Types.TINYINT:
case Types.BIGINT:
case Types.DECIMAL: //for Oracle Driver
case Types.DOUBLE: //for Oracle Driver
case Types.FLOAT: //for Oracle Driver
return true;
case Types.CHAR:
case Types.LONGVARCHAR:
case Types.VARCHAR:
return false;
default:
throw new HibernateException("Unable to persist an Enum in a column of SQL Type: " + paramType);
}
}

public Object deepCopy(final Object value) throws HibernateException {
return value;
}

public boolean isMutable() {
return false;
}

public Serializable disassemble(final Object value) throws HibernateException {
return (Serializable) value;
}

public Object assemble(final Serializable cached, final Object owner) throws HibernateException {
return cached;
}

public Object replace(final Object original, final Object target, final Object owner) throws HibernateException {
return original;
}
}


Now we need to create a class which will be mapped to bd from one side and will wrap our Education enum from the other. Let's call it EducationEnumType :

public final class EducationEnumType extends EnumType{

/* ((( Constructors ))) */
/**
/*
*/
public EducationEnumType() {
super(Education.class);
}
}


We are almost finished. All is left is main part of our User.hbm.xml file : we need to create a property called "education" with type of EducationEnumType (NOT Education!).

That's all. Enjoy enumerations in hibernate :)

Tuesday, April 3, 2007

Hibernate & enumerations

Recently I had problem with mapping of enumerations. My database was MySQL and I used hibernate annotations. There are two solutions for doing that :

1 solution.

For storing enumerations in db we use field of type integer, it means that we store only indexes of items in enumeration. Let's make simple example:
- our db script :

CREATE DATABASE example DEFAULT CHARSET=utf8;

CREATE TABLE CalendarDay (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
day_type INTEGER NULL,
PRIMARY KEY(id),
);


- part of our entity class:

@Entity
@Table(name="CALENDARDAY")
public class CalendarDay implements Serializable {

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id = null;

@Column(name="DAY_TYPE")
@Enumerated()
private DayType dayType;

...


- our enumeration :

public enum DayType {
WORKINGDAY,
RESTDAY,
HOLIDAY,
}


If we create an instance of class CalendarDay and put it into db, in field DAY_TYPE we will only see 0,1,2 (indexes which corresponds to our enums : WORKINGDAY, RESTDAY, HOLIDAY).

Advantages of this solution are :
- less memory for storing data (as we store only integers);
Disadvantages of this solution are :
- we could not change order in which enums are written in DayType;
- store data is not so obvious to read and understand, as we see only integers, not strings;

2 solution:

For storing enumerations in db we use field of type varchar it means that we store strings of enumeration. Let's make simple example:

- our db script :

CREATE DATABASE example DEFAULT CHARSET=utf8;

CREATE TABLE CalendarDay (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
day_type VARCHAR(45) NULL,
PRIMARY KEY(id),
);


- part of our entity class:

@Entity
@Table(name="CALENDARDAY")
public class CalendarDay implements Serializable {

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id = null;

@Column(name="DAY_TYPE")
@Enumerated(EnumType.STRING)
private DayType dayType;

...


- our enumeration :

public enum DayType {
WORKINGDAY,
RESTDAY,
HOLIDAY,
}


If we create an instance of class CalendarDay and put it into db, in field DAY_TYPE we will see strings which corresponds to our enums : WORKINGDAY, RESTDAY, HOLIDAY).

Advantages of this solution are :
- we could not change order in which enums are written in DayType;
- store data is more obvious to read and understand;
Disadvantages of this solution are :
- a lot of memory for storing data (as we store full strings);

Thursday, March 22, 2007

Tapestry 4 localization problem

I decided to add localization to my web application. I created file .properties, where I added some pairs : key - value. When I again entered on some of my pages - exception happened : No message found under code 'alt' for locale 'ru_RU' (org.springframework.context.NoSuchMessageException). This problem occurred cause to DatePicker component, which wanted to find localization parameter called "clear" and couldn't find it in .properties file. The simpliest solution for this problem was to all fictive parameter called "clear" with any value (for instance, "CLEAR"). In finding the root of this problem I didn't succeed.

Tuesday, March 20, 2007

MySQL encoding problem

This problem is very common and solution for it can be found very easily. But when I faced it, it was not so easy to find appropriate solution for me. So, I have MySQL db. I saved there data (for instance, strings) written in Russian (or Ukrainian). But when I wanted to view data (through MySQL console or browser in my web application) there were only ???????? (question marks). I tested all posted solutions in Internet (to change my.ini, to change sql script), but none helped. My founded solution was simply to set char set while creating db. For example :
CREATE DATABASE DEFAULT CHARSET=utf8;
USE ;
SET NAMES utf8;

Encoding problem with tomcat

Recently I had problem with encoding. Now I'm going to describe it. I created simple html template which used russian (or ukrainian) letters. Saved this template in UTF-8 encoding. When I view this template file in firefox or IE everything was written good. But when I wrapped it into war and put it into tomcat/webapp directory and tried to view it like localhost:8080/domain/site, I could not read a word. Firefox or IE also showed that current encoding is UTF-8. Changing of all encodings didn't help. The solution for current problem was to add option to JAVA_OPTS in catalina.bat (or catalina.sh in Linux) like -Dfile.encoding=UTF-8.