Mapping a FunctionalJava Option<Type> with Hibernate
Asked Answered
P

3

6

I have a hibernate-mapped Java object, JKL, which is full of a bunch of normal hibernate-mappable fields (like strings and integers).

I'm added a new embedded field to it (which lives in the same table -- not a mapping), asdf, which is a fj.data.Option<ASDF>. I've made it an option to make it clear that this field may not actually contain anything (as opposed to having to handle null every time I access it).

How do I set up the mapping in my JKL.hbm.xml file? I'd like hibernate to automatically convert a null in the database to a none of fj.data.Option<ASDF> when it retrieves the object. It should also convert a non-null instance of ASDF to a some of fj.data.Option<ASDF>.

Is there any other trickery that I have to do?

Pullman answered 15/5, 2012 at 16:17 Comment(0)
Z
12

I would suggest introducing FunctionalJava's Option in the accessors (getter and setter), while leaving Hibernate to handle a simple java field which is allowed to be null.

For example, for an optional Integer field:

// SQL
CREATE TABLE `JKL` (
    `JKL_ID` INTEGER PRIMARY KEY,
    `MY_FIELD` INTEGER DEFAULT NULL
)

You can map a Hibernate private field directly:

// Java
@Column(nullable = true)
private Integer myField;

You could then introduce Option at the accessor boundary:

// Java
public fj.data.Option<Integer> getMyField() {
    return fj.data.Option.fromNull(myField);
}

public void setMyField(fj.data.Option<Integer> value) {
    myField = value.toNull();
}

Does that work for your needs?

Zetta answered 15/5, 2012 at 16:34 Comment(0)
O
2

You can use Hibernate's custom mapping types. Documentation is here. Here is an analogous example of mapping Scala's Option to a Hibernate mapping.

Simply put, you would need to extend the org.hibernate.UserType interface. You could also create a generic-typed base class with a JKL-typed sub-type, similar to what you see in the Scala example.

Ohmmeter answered 15/5, 2012 at 17:29 Comment(0)
F
0

I think using getter/setter is simpler, but here's an example of what I did to make it work :

(It works fine for number and string, but not for date (error with @Temporal annotation)).

import com.cestpasdur.helpers.PredicateHelper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
import org.joda.time.DateTime;

import java.io.Serializable;
import java.sql.*;

public class OptionUserType implements UserType {


@Override
public int[] sqlTypes() {
    return new int[]{
            Types.NULL
    };
}

@Override
public Class returnedClass() {
    return Optional.class;
}

@Override
public boolean equals(Object o, Object o2) throws HibernateException {
    return ObjectUtils.equals(o, o2);

}

@Override
public int hashCode(Object o) throws HibernateException {
    assert (o != null);
    return o.hashCode();
}

@Override
public Optional<? extends Object> nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
    return Optional.fromNullable(rs.getObject(names[0]));
}

@VisibleForTesting
void handleDate(PreparedStatement st, Date value, int index) throws SQLException {
    st.setDate(index, value);
}

@VisibleForTesting
void handleNumber(PreparedStatement st, String stringValue, int index) throws SQLException {
    Double doubleValue = Double.valueOf(stringValue);
    st.setDouble(index, doubleValue);
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {

    if (value != null) {
        if (value instanceof Optional) {
            Optional optionalValue = (Optional) value;
            if (optionalValue.isPresent()) {
                String stringValue = String.valueOf(optionalValue.get());


                if (StringUtils.isNotBlank(stringValue)) {

                    if (PredicateHelper.IS_DATE_PREDICATE.apply(stringValue)) {
                        handleDate(st, new Date(DateTime.parse(stringValue).getMillis()), index);
                    } else if (StringUtils.isNumeric(stringValue)) {
                        handleNumber(st, stringValue, index);
                    } else {
                        st.setString(index, optionalValue.get().toString());
                    }
                } else {
                    st.setString(index, null);
                }


            } else {
                System.out.println("else Some");
            }

        } else {
            //TODO replace with Preconditions guava
            throw new IllegalArgumentException(value + " is not implemented");

        }
    } else {
        st.setString(index, null);

    }


}

@Override
public Object deepCopy(Object o) throws HibernateException {
    return o;
}

@Override
public boolean isMutable() {
    return false;
}

@Override
public Serializable disassemble(Object o) throws HibernateException {
    return (Serializable) o;
}

@Override
public Object assemble(Serializable serializable, Object o) throws HibernateException {
    return serializable;
}

@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
    return original;
}
}
Frogman answered 11/5, 2013 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.