Question

I am using Hibernate as my JPA provider with it connecting to a Progress database. When a NaN value is persisted it is causing lots of problems - it prevents the row from being read in certain circumstances. Is there a way to hook in to the standard double type persistence to convert NaN (and probably + and - infinity) to a different value? It does not matter if the NaN or infinity information is lost, I just want a readable row!

I know I could do something like this:

@Column(name = "doubleColumn")
public double getDoubleColumn() {
    return PersistedDouble.convert(doubleColumn);
}

But I am worried about maintenance as that will have to be manually added for any doubles mapped to the database.

Was it helpful?

Solution

My first impression of this would be to look for type that Hibernate persists double as. So you can refactor the set(...) method in DoubleType. That would mean though that you would need to annotate every Double type with @org.hibernate.annotations.type(type="myDouble") after you have defined "myDouble" using @org.hibernate.annotations.TypeDef in package-info -- I think you want to avoid all this for maintainance (beyond that you would have to go into the heart of Hibernate).

OTHER TIPS

You could modify hibernate itself. All you have to do is change the class DoubleType.

Of course, you'd then have to maintain that patch as hibernate evolves, but considering it's in a single, rather stable class this might be easier than specifying a UserType for every double in your domain model.

Following this discussion, I have the feeling, that hibernate doesn't offer a way to convert NaN into something else. I think, you have to prevent NaN values earlier, even before they're written to the bean member variables (like adding guard/conversion code to the setters).

EDIT

I fear, the best unpleasant solution is to use guard code and, which is even worse, an additional column on the table, to flag, whether the value is a number or not. What certainly will complicate the query and insert operations. But you need NaN in the database and you can't fight the jdbc driver/database to behave properly (and accept NaN as valid inputs for NUMBER fields).

I used the UserType solution in the end but solved the maintenance issue with a unit test. The type class is as follows:

public class ParsedDoubleType extends DoubleType {
    private static final long serialVersionUID = 1L;

    @Override
    public void set(PreparedStatement st, Object value, int index) throws SQLException {
        Double doubleValue = (Double) value;
        if (doubleValue.isInfinite() || doubleValue.isNaN()) {
            Logger.getLogger(ParsedDoubleType.class).warn("Attempted to send a NaN or infinity value to the database " 
                + "- this is not supported.\nStatement=" + st + " valueIndex=" + index);
            doubleValue = Double.valueOf(0);
        }
        super.set(st, doubleValue, index);
    }
}

The unit test is roughly (some details removed for brevity):

Ejb3Configuration hibernateConfig = new Ejb3Configuration().configure("InMemoryDatabasePersistenceUnit", null);
for (Iterator<?> iterator = hibernateConfig.getClassMappings(); iterator.hasNext();) {
    PersistentClass clazz = (PersistentClass) iterator.next();
    Iterator<?> propertyIterator = clazz.getPropertyIterator();
    while (propertyIterator.hasNext()) {
        if (property.getType().equals(Hibernate.DOUBLE)) {
            Assert.fail("Raw double type found. Annotate with @Type(type = \"package.ParsedDoubleType\")\n" 
                + "Class " + clazz + " property " + property);
        }
    }
}

I had exactly the same problem, and with the guidance of these solutions, I also prepared a custom type class extending DoubleType. Inside that class I converted NaN values to null at the set function and vice versa for the get function, since null is OK for my database columns. I also changed the mapping for NaN possible columns to the custom type class. That solution worked perfectly for hibernate 3.3.2.

Unfortunately, after upgrading Hibernate to 3.6.10, it stopped working. In order to make it work again, I replaced the custom type from extending DoubleType to implement UserType.

The important data type function implementations should be as follows:

private int[] types = { Types.DOUBLE };

public int[] sqlTypes()
{
    return types;
}

@SuppressWarnings("rawtypes")
public Class returnedClass()
{
    return Double.class;
}

And here are the get and set functions:

public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException
{
    Double value = rs.getDouble(names[0]);
    if (rs.wasNull())
        return Double.NaN;
    else
        return value;
}

public void nullSafeSet(PreparedStatement ps, Object value, int index) throws HibernateException, SQLException
{
    Double dbl = (Double) value;
    if ((dbl == null) || (Double.isNaN(dbl)))
        ps.setNull(index, Types.DOUBLE);
    else
        ps.setDouble(index, dbl);
}

sorry but judging from your examples and your question you actually have problems understanding java persistence. Database-entities are self-managed via getters and setters - these can do any validation you would like them to have. If you really set attributes without them, you're missing core concepts of object-oriented development AND persistence -- managed entities in particular. Methinks you need to re-engineer your project, having problems like these are a sure sign of fundamental design flaws ... just giving some advice here - and thats the solution :

@Column(name="doubleColumn"}
private Double doubleColumn = Double.NaN  //yes, this is intentional. Verily.

public void setDouble(Double d)
{
    if(d.isNan || d.isInfinite()
    {
       //do something nice here
    }
    else
       this.doubleColumn = d;
}
public Double getDouble()
{
   return !this.doubleColumn.isNaN() && !this.doubleColumn.isInfinite() ? this.doubleColumn : new Double();
}

.... its that easy.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top