Переопределение BeanPropertyRowMapper для поддержки JodaTime DateTime

Объект My Domain имеет пару полей Joda-Time DateTime. Когда я читаю значения базы данных с помощью SimpleJdbcTemplate:

Пациент пациента = jdbc.queryForObject(sql, new BeanPropertyRowMapper (Patient.class), patientId);

Он просто терпит неудачу и неожиданно, ошибки не были зарегистрированы. Я думаю, это из-за синтаксического анализа timestamp на DateTime не работает с Jdbc.

Если возможно наследовать и переопределить BeanPropertyRowMapper и указать, чтобы преобразовать все java.sql.Timestamp и java.sql.Date в DateTime, это было бы здорово и могло бы сэкономить много дополнительного кода.

Любые советы?

Ответ 1

Правильная вещь - подкласс BeanPropertyRowMapper, переопределить initBeanWrapper(BeanWrapper) и зарегистрировать собственный редактор свойств:

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

Посмотрев на реализацию BeanPropertyRowMapper, он устанавливает поля:

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

где getColumnValue(rs, index, pd); делегирует JdbcUtils.getResultSetValue

Это поле pd в getColumnValue является фактическим " p ростом d escriptor", который используется (pd.getPropertyType()) в JdbcUtils как тип поля для отображения.

Если вы посмотрите код JdbcUtils для метода getResultSetValue, вы увидите, что он просто переходит из одного оператора if в другой, чтобы соответствовать pd.getPropertyType() ко всем стандартным типам. Когда он не находит его, поскольку DateTime не является "стандартным" типом, он полагается на rs.getObject():

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

Затем, если этот объект является SQL Date, он преобразует его в Timestamp и возвращается для установки в поле DateTime вашего домена = > , где он терпит неудачу.

Следовательно, кажется, что нет прямого способа вставить преобразователь Date/Timestamp в DateTime в BeanPropertyRowMapper. Таким образом, было бы проще (и более опытным) реализовать свой собственный RowMapper.

Если вы хотите увидеть ошибку сопоставления в консоли, установите уровень ведения журнала для org.springframework.jdbc для "отладки" или, еще лучше, "трассировки", чтобы увидеть, что именно происходит.

Одна вещь, которую вы можете попробовать, которую я не тестировал, заключается в расширении BeanPropertyRowMapper и переопределении свойства типа DateTime:

/**
 * 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

Ответ @Sean Patrick Floyd идеален, пока у вас не будет много разных пользовательских типов.

Здесь он обобщен, настраивается на основе расширения использования:

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