views:

205

answers:

4

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.

+1  A: 

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).

Andreas_D
Thanks for that - I was hoping there may be something that the participants in that discussion had missed. The guard (or for that matter UserType) solution suffer from the same maintenance issue as I described in the question. Plus in this situation NaN is actually the correct result so it feels improper to make it illegal at the object level (even if it gets lost when it is mapped).
Russ Hayward
That's bad - because it's obviously databases and jdbc drivers that have problems handling NaN, not hibernate... and this NaN problem seems to prevent from a portable (database independent) solution...
Andreas_D
Yeah this is definitely a JDBC issue - I was hoping Hibernate would be able to get round the bugs for me! Anyway I wish I could accept your answer too but SO will only allow me to choose one.
Russ Hayward
+3  A: 

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).

non sequitor
That seems like the best solution - thanks for your help. 137 @Type annotations later and it works!
Russ Hayward
+1  A: 

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.

meriton
This is definitely correct - unfortunately SO will only allow me to accept one answer. I considered it but as you mentioned it is a bit of a pain when Hibernate updates. Thanks for your help.
Russ Hayward
A: 

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);
        }
    }
}
Russ Hayward