2011-03-06 16 views
8

Estoy tratando de persistir los campos JodaTime DateTime con JPA a PostgreSQL, pero se encuentran con problemas con punteros nulos a los valores NULL de la base de datos.JodaTime con valores JPA, PostgreSQL y NULL

Estoy trabajando con NetBeans 7 beta 2 IDE. La implementación de persistencia es EclipseLink 2.2.0 y estoy usando un convertidor EclipseLink para que la asignación funcione. Aquí está la declaración de mi campo:

@Converter(
    name="dateTimeConverter", 
    converterClass=ejb.util.DateTimeConverter.class 
) 
@Column(columnDefinition="TIMESTAMP WITH TIME ZONE") 
@Convert("dateTimeConverter") 
private DateTime testdate; 

La clase convertidor:

public class DateTimeConverter implements Converter { 

    private Logger log; 
    private static final long serialVersionUID = 1L; 

    @Override 
    public Object convertObjectValueToDataValue(Object o, Session sn) { 
     if (o == null) { 
      log.info("convertObjectValueToDataValue returning null"); 
      return null; 
     } 
     return ((DateTime)o).toDate(); 
    } 

    @Override 
    public Object convertDataValueToObjectValue(Object o, Session sn) { 
     if (o == null) { 
      log.info("convertDataValueToObjectValue returning null"); 
      return null; 
     } 
     return new DateTime(o); 
    } 

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

    @Override 
    public void initialize(DatabaseMapping dm, Session sn) { 
     log = Logger.getLogger("ejb.util.DateTimeConverter"); 
    } 

} 

Esto funciona bien siempre y cuando hay un conjunto DateTime real. Pero tan pronto como no está configurado, EclipseLink parece asumir un tipo de cadena y postgresql comienza a quejarse de que el valor del carácter de tipo varía. Supongo que esto se debe a que la clase de convertidor devuelve un puntero nulo en lugar de un objeto de fecha y EclipseLink vuelve a tener un valor predeterminado.

¿Hay alguna manera de hacer que esto funcione menos de cambiar a java.util.Date simple?

+1

¿Ha intentado agregar la anotación '@ Temporal' al campo 'dateTime testdate' privado? –

+0

Sí, pero cuando implemento, EclipseLink realmente registra un mensaje que ignorará la anotación @Temporal debido al convertidor. – Eelke

Respuesta

7

Cuando se usa @Converter, EclipseLink no conoce el tipo, por lo que debe inicializarlo.

En el método de initialize(DatabaseMapping dm, Session sn) debe configurar el tipo,

dm.setFieldClassification(java.sql.Date.class); 
// or, dm.setFieldClassification(java.sql.Timestamp.class); 
+0

Lástima, el método setFieldClassification no existe de acuerdo con netbeans y los documentos, estoy usando 2.2.0. Hay un getter con ese nombre. Hubiera sido una buena solución. – Eelke

+2

Encontrado, está definido en la subclase AbstractDirectMapping. Entonces, después de verificar el tipo y emitirlo, está funcionando. Gracias. – Eelke

+0

¡Gracias! Me salvó el día :) –

0

Hemos utilizado un enfoque diferente. (! Y disculpas por nuestros estándares de codificación)

En el POJO tenemos:

@Column(name="LAST_UPDATED") 
    @Type(type="DateTime") 
    @NotNull 
    public LastUpdType getLastUpdated() 
    { 
    return mLastUpdated; 
    } 

En package.info tenemos:

@TypeDefs(
    { 
    @TypeDef(name = "DateTime", typeClass = JodaDateTimeType.class) 
    }) 

entonces tenemos la clase JodaDateTimeType

package uk.co.foo.hibernateutils.type; 
import java.io.Serializable; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 
import java.sql.Timestamp; 
import java.sql.Types; 
import org.apache.log4j.Logger; 
import org.hibernate.HibernateException; 
import org.hibernate.cfg.Environment; 
import org.hibernate.usertype.UserType; 
import org.joda.time.DateTime; 


public class JodaDateTimeType implements UserType 
{ 
    private Logger mLogger = Logger.getLogger(getClass()); 

    /** 
    * Implementation taken from org.hibernate.type.MutableType via 
    * org.hibernate.type.CalendarType. 
    * @return true if the field is mutable, false otherwise. 
    * 
    * @see org.hibernate.type.Type#isMutable() 
    */ 
    public boolean isMutable() 
    { 
    return true; 
    } 

    /** 
    * @param aRs A JDBC result set 
    * @param aNames The column names 
    * @param aOwner The containing entity 
    * @return The retrieved value. 
    * @throws HibernateException If a HibernateException occurs. 
    * @throws SQLException If a SQLException occurs. 
    * 
    * @see org.hibernate.usertype.UserType 
    *  #nullSafeGet(java.sql.ResultSet, java.lang.String[], 
    *     java.lang.Object) 
    */ 
    public Object nullSafeGet(ResultSet aRs, String[] aNames, Object aOwner) 
     throws HibernateException, SQLException 
    { 
    return nullSafeGet(aRs, aNames[0]); 
    } 

    /** 
    * Implementation taken mainly from org.hibernate.type.NullableType. 
    * 
    * @param aRs The resultset containing db data. 
    * @param aName The name of the required value. 
    * @return The retrieved value. 
    * @throws HibernateException If a HibernateException occurs. 
    * @throws SQLException If a SQLException occurs. 
    */ 
    public Object nullSafeGet(ResultSet aRs, String aName) 
     throws HibernateException, SQLException 
    { 
    try 
    { 
     Object value = get(aRs, aName); 
     if (value == null || aRs.wasNull()) 
     { 
     if (mLogger.isDebugEnabled()) 
     { 
      mLogger.debug("returning null as column: " + aName); 
     } 
     return null; 
     } 
     else if (mLogger.isDebugEnabled()) 

     { 
     mLogger 
      .debug("returning '" + toString(value) + "' as column: " + aName); 
     } 
     return value; 

    } 
    catch (RuntimeException re) 
    { 
     mLogger.info("could not read column value from result set: " + aName 
     + "; " + re.getMessage()); 
     throw re; 
    } 
    catch (SQLException se) 
    { 
     mLogger.info("could not read column value from result set: " + aName 
     + "; " + se.getMessage()); 
     throw se; 
    } 
    } 

    /** 
    * Implementation mainly taken from org.hibernate.type.CalendarType. 
    * 
    * @param aRs The resultset containing db data. 
    * @param aName The name of the required value. 
    * @return The retrieved value. 
    * @throws HibernateException If a HibernateException occurs. 
    * @throws SQLException If a SQLException occurs. 
    */ 
    protected Object get(ResultSet aRs, String aName) throws HibernateException, 
     SQLException 
    { 
    Timestamp ts = aRs.getTimestamp(aName); 
    if (ts != null) 
    { 
     DateTime dateTime; 
     if (Environment.jvmHasTimestampBug()) 
     { 
     dateTime = new DateTime(ts.getTime() + ts.getNanos()/1000000); 
     } 
     else 
     { 
     dateTime = new DateTime(ts.getTime()); 
     } 
     return dateTime; 
    } 
    return null; 
    } 

    /** 
    * Implementation taken mainly from org.hibernate.type.NullableType. 
    * 
    * @param aSt A JDBC prepared statement 
    * @param aValue The object to write 
    * @param aIndex Statement parameter index 
    * @throws HibernateException If a HibernateException occurs. 
    * @throws SQLException If a SQLException occurs. 
    */ 
    public void nullSafeSet(PreparedStatement aSt, Object aValue, int aIndex) 
     throws HibernateException, SQLException 
    { 
    try 
    { 
     if (aValue == null) 
     { 
     if (mLogger.isDebugEnabled()) 
     { 
      mLogger.debug("binding null to parameter: " + aIndex); 
     } 

     aSt.setNull(aIndex, sqlType()); 
     } 
     else 
     { 
     if (mLogger.isDebugEnabled()) 
     { 
      mLogger.debug("binding '" + toString(aValue) + "' to parameter: " 
      + aIndex); 
     } 

     set(aSt, aValue, aIndex); 
     } 
    } 
    catch (RuntimeException re) 
    { 
     mLogger.info("could not bind value '" + nullSafeToString(aValue) 
     + "' to parameter: " + aIndex + "; " + re.getMessage()); 
     throw re; 
    } 
    catch (SQLException se) 
    { 
     mLogger.info("could not bind value '" + nullSafeToString(aValue) 
     + "' to parameter: " + aIndex + "; " + se.getMessage()); 
     throw se; 
    } 
    } 

    /** 
    * Implementation mainly taken from org.hibernate.type.CalendarType. 
    * 
    * @param aSt A JDBC prepared statement 
    * @param aValue The object to write 
    * @param aIndex Statement parameter index 
    * @throws HibernateException If a HibernateException occurs. 
    * @throws SQLException If a SQLException occurs. 
    */ 
    protected void set(PreparedStatement aSt, Object aValue, int aIndex) 
     throws HibernateException, SQLException 
    { 
    aSt.setTimestamp(aIndex, new Timestamp(((DateTime) aValue).getMillis())); 
    } 

    /** 
    * Implementation mainly taken from org.hibernate.type.NullableType. A 
    * null-safe version of {@link #toString(Object)}. Specifically we are 
    * worried about null safeness in regards to the incoming value parameter, not 
    * the return. 
    * 
    * @param aValue The value to convert to a string representation; may be null. 
    * @return The string representation; may be null. 
    * @throws HibernateException Thrown by {@link #toString(Object)}, which this 
    *   calls. 
    */ 
    private String nullSafeToString(Object aValue) throws HibernateException 
    { 
    if (aValue != null) 
    { 
     return toString(aValue); 
    } 

    return null; 
    } 

    /** 
    * @param aValue value of the correct type. 
    * @return A string representation of the given value. 
    * @throws HibernateException If a HibernateException occurs. 
    */ 
    private String toString(Object aValue) throws HibernateException 
    { 
    return ((DateTime) aValue).toString(); 
    } 

    /** 
    * 
    * @return Types.DATE 
    */ 
    private int sqlType() 
    { 
    return Types.TIMESTAMP; 
    } 

    /** 
    * @return The class returned by nullSafeGet. 
    * @see org.hibernate.usertype.UserType#returnedClass() 
    */ 
    public Class<?> returnedClass() 
    { 
    return DateTime.class; 
    } 

    /** 
    * @param aX First object of type returned by returnedClass. 
    * @param aY Second object of type returned by returnedClass. 
    * @return True if the objects are equal, false otherwise. 
    * @see org.hibernate.usertype.UserType 
    *  #equals(java.lang.Object, java.lang.Object) 
    * @throws HibernateException to conform to superclass signature 
    */ 
    public boolean equals(Object aX, Object aY) throws HibernateException 
    { 
    if (aX == null) 
    { 
     return aY == null; 
    } 

    return aX.equals(aY); 
    } 

    /** 
    * @param aX Object of type returned by returnedClass. 
    * @return Hashcode of given object. 
    * @see org.hibernate.usertype.UserType#hashCode(java.lang.Object) 
    * @throws HibernateException to conform to superclass signature 
    */ 
    public int hashCode(Object aX) throws HibernateException 
    { 
    if (aX == null) 
    { 
     return -1; 
    } 

    return aX.hashCode(); 
    } 

    /** 
    * @return The sql typecodes. 
    * @see org.hibernate.usertype.UserType#sqlTypes() 
    */ 
    public int[] sqlTypes() 
    { 
    return new int[] {sqlType()}; 
    } 

    /** 
    * Implementation taken from 
    * org.springframework.orm.hibernate3.support.AbstractLobType. 
    * @param aCached The object to be cached. 
    * @param aOwner The owner of the cached object. 
    * @return A reconstructed object from the cachable representation. 
    * @throws HibernateException If a HibernateException occurs. 
    * 
    * @see org.hibernate.usertype.UserType 
    *  #assemble(java.io.Serializable, java.lang.Object) 
    */ 
    public Object assemble(Serializable aCached, Object aOwner) 
     throws HibernateException 
    { 
    return aCached; 
    } 

    /** 
    * @param aValue the object to be cloned, which may be null. 
    * @return A copy of the given object. 
    * @see org.hibernate.usertype.UserType#deepCopy(java.lang.Object) 
    * @throws HibernateException to conform to superclass signature 
    */ 
    public Object deepCopy(Object aValue) throws HibernateException 
    { 
    if (aValue != null) 
    { 
     return new DateTime(((DateTime) aValue).getMillis()); 
    } 

    return null; 

    } 

    /** 
    * Implementation taken from 
    * org.springframework.orm.hibernate3.support.AbstractLobType. 
    * @param aValue The object to be cached. 
    * @return A cachable representation of the object. 
    * 
    * @see org.hibernate.usertype.UserType#disassemble(java.lang.Object) 
    * @throws HibernateException to conform to superclass signature 
    */ 
    public Serializable disassemble(Object aValue) throws HibernateException 
    { 
    return (Serializable) aValue; 
    } 

    /** 
    * Implementation taken from 
    * org.springframework.orm.hibernate3.support.AbstractLobType. 
    * @param aOriginal The value from the detached entity being merged 
    * @param aTarget The value in the managed entity 
    * @param aOwner The owner of the cached object. 
    * @return The value to be merged 
    * 
    * @see org.hibernate.usertype.UserType 
    *  #replace(java.lang.Object, java.lang.Object, java.lang.Object) 
    * @throws HibernateException to conform to superclass signature 
    */ 
    public Object replace(Object aOriginal, Object aTarget, Object aOwner) 
     throws HibernateException 
    { 
    return aOriginal; 
    } 
} 
+0

El uso de Hibernate está un poco alejado de mi camino, pero lo tendré en cuenta. Thx – Eelke

+0

¿Por qué escribir su propio tipo? Hay http://joda-time.sourceforge.net/contrib/hibernate/userguide.html –

1

Este artículo: http://www.thoughts-on-java.org/persist-localdate-localdatetime-jpa/ me dio algunos inf bastante útil o al persistir la fecha local en el archivo db y tenía algunas diferencias importantes con su implementación DateTimeConverter (implementando específicamente AttributeConverter no solo el convertidor y la anotación de 'Converter' está en el nivel de clase

+0

Gracias, se ve bien, es posible que desee incorporar un poco más de detalles y/o un ejemplo en su respuesta porque como su respuesta ahora lo soporta puede marcarse como una respuesta de solo enlace. Por cierto, la solución en el artículo requiere JPA2.1 que no existía cuando originalmente publiqué la pregunta. – Eelke

+0

No estaba seguro de la 'etiqueta' de copiar/pegar desde la fuente, es decir, vincularla a compartirla en su totalidad aquí.No quise dar a entender que tu solución era incorrecta (¡se me preguntó hace casi 5 años! :)). Solo quería agregar esta información adicional aquí para otros que empezaron en la dirección correcta de los comentarios aquí y luego necesitaron ayuda adicional. – janoulle

Cuestiones relacionadas