Overriding BeanPropertyRowMapper to Support JodaTime DateTime

ε祈祈猫儿з 提交于 2019-12-31 21:35:13

问题


My Domain object has couple of Joda-Time DateTime fields. When I'm reading database values using SimpleJdbcTemplate:

Patient patient = jdbc.queryForObject(sql, new BeanPropertyRowMapper(Patient.class), patientId);

It just fails and surprisingly, no errors were logged. I guess it's because of the timestamp parsing to DateTime is not working with Jdbc.

If it's possible to inherit and override BeanPropertyRowMapper and instruct to convert all java.sql.Timestamp and java.sql.Date to DateTime, it would be great and could save a lot of extra code.

Any advice?


回答1:


The correct thing to do is to subclass BeanPropertyRowMapper, override initBeanWrapper(BeanWrapper) and register a custom Property Editor:

public class JodaDateTimeEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(final String text) throws IllegalArgumentException {
        setValue(new DateTime(text)); // date time in ISO8601 format
                                      // (yyyy-MM-ddTHH:mm:ss.SSSZZ)
    }
    @Override
    public void setValue(final Object value) {
        super.setValue(value == null || value instanceof DateTime ? value
                                        : new DateTime(value));
    }
    @Override
    public DateTime getValue() {
        return (DateTime) super.getValue();
    }
    @Override
    public String getAsText() {
        return getValue().toString(); // date time in ISO8601 format
                                      // (yyyy-MM-ddTHH:mm:ss.SSSZZ)
    }
}
public class JodaTimeSavvyBeanPropertyRowMapper<T>
                  extends BeanPropertyRowMapper<T> {
    @Override
    protected void initBeanWrapper(BeanWrapper bw) {
        bw.registerCustomEditor(DateTime.class, new JodaDateTimeEditor());
    }
}



回答2:


Looking at BeanPropertyRowMapper implementation, the way it sets the fields is:

Object value = getColumnValue( rs, index, pd );

if (logger.isDebugEnabled() && rowNumber == 0) {
    logger.debug("Mapping column '" + column + "' to property '" +
    pd.getName() + "' of type " + pd.getPropertyType());
}
try {
    bw.setPropertyValue(pd.getName(), value);
}

where getColumnValue(rs, index, pd); delegates to JdbcUtils.getResultSetValue

That pd field in getColumnValue is the actual "p roperty d escriptor", that is used ( pd.getPropertyType() ) in JdbcUtils as a type of the field to map to.

If you look at JdbcUtils code for getResultSetValue method, you'll see that it simply goes from one if statement to another, to match pd.getPropertyType() to all standard types. When it does not find one, since DateTime is not a "standard" type, it relies on a rs.getObject():

} else {
// Some unknown type desired -> rely on getObject.

Then if this object is a SQL Date it converts it to a Timestamp, and returns to be set to a DateTime field of your domain => where it fails.

Hence, there does not seem to be a straight forward way to inject a Date/Timestamp to DateTime converter into a BeanPropertyRowMapper. So it would be cleaner (and more performant) to implement your own RowMapper.

In case you'd like to see the mapping error in a console, set your logging level for org.springframework.jdbc to "debug" or better yet "trace" to see exactly what happens.

One thing you can try, which I have not tested, is to extend a BeanPropertyRowMapper and override a property of DateTime type in:

/**
 * Initialize the given BeanWrapper to be used for row mapping.
 * To be called for each row.
 * <p>The default implementation is empty. Can be overridden in subclasses.
 * @param bw the BeanWrapper to initialize
 */
 protected void initBeanWrapper(BeanWrapper bw) {}



回答3:


The answer of @Sean Patrick Floyd is perfect until you do not have many many custom types.

Here it a generalized, configurable based on usage extension:

public class CustomFieldTypeSupportBeanPropertyRowMapper<T> extends BeanPropertyRowMapper<T> {
  private Map<Class<?>, Handler> customTypeMappers = new HashMap<Class<?>, Handler>();

  public CustomFieldTypeSupportBeanPropertyRowMapper() {
    super();
  }

  public CustomFieldTypeSupportBeanPropertyRowMapper(Class<T> mappedClass, boolean checkFullyPopulated) {
    super(mappedClass, checkFullyPopulated);
  }

  public CustomFieldTypeSupportBeanPropertyRowMapper(Class<T> mappedClass) {
    super(mappedClass);
  }

  public CustomFieldTypeSupportBeanPropertyRowMapper(Class<T> mappedClass, Map<Class<?>, Handler> customTypeMappers) {
    super(mappedClass);
    this.customTypeMappers = customTypeMappers;
  }

  @Override
  protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException {
    final Class<?> current = pd.getPropertyType();
    if (customTypeMappers.containsKey(current)) {
      return customTypeMappers.get(current).f(rs, index, pd);
    }
    return super.getColumnValue(rs, index, pd);
  }

  public void addTypeHandler(Class<?> class1, Handler handler2) {
    customTypeMappers.put(class1, handler2);
  }

  public static interface Handler {
    public Object f(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException;
  }
}


来源:https://stackoverflow.com/questions/7730529/overriding-beanpropertyrowmapper-to-support-jodatime-datetime

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!