JodaTime有JPA,PostgreSQL和NULL值

时间:2011-03-06 10:24:31

标签: java postgresql jpa eclipselink jodatime

我正在尝试将带有JPA的JodaTime DateTime字段保存到PostgreSQL,但是遇到了指向数据库NULL值的空指针的麻烦。

我正在使用NetBeans 7 beta 2 IDE。持久性实现是EclipseLink 2.2.0,我使用EclipseLink Converter来使映射工作。这是我的领域的声明:

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

转换器类:

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");
    }

}

只要设置了实际的DateTime,这就可以正常工作。但是一旦没有设置,EclipseLink似乎就会假设一个字符串类型,并且postgresql开始抱怨类型字符的值变化。我假设这是因为转换器类返回空指针而不是日期对象,而EclipseLink则返回默认值。

有没有办法让它无法切换到普通的java.util.Date?

3 个答案:

答案 0 :(得分:7)

当使用@Converter时,EclipseLink不知道类型,因此您需要初始化它。

initialize(DatabaseMapping dm, Session sn)方法中,您需要设置类型

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

答案 1 :(得分:1)

这篇文章:http://www.thoughts-on-java.org/persist-localdate-localdatetime-jpa/给了我一些关于将localdate持久保存到数据库的非常有用的信息,它与你的DateTimeConverter实现有一些重要的区别(具体实现了AttributeConverter而不仅仅是转换器和'Converter'的注释是班级

答案 2 :(得分:0)

我们使用了不同的方法。    (并为我们的编码标准道歉!)

在POJO中我们有:

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

在package.info中我们有:

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

然后我们有了类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;
  }
}