重写 BeanPropertyRowMapper 以支持 JodaTime DateTime

发布于 2024-12-09 10:43:36 字数 444 浏览 1 评论 0原文

我的 Domain 对象有几个 Joda-Time DateTime 字段。当我使用 SimpleJdbcTemplate 读取数据库值时:

病人病人 = jdbc.queryForObject(sql, new BeanPropertyRowMapper(Patient.class), PatientId);

它只是失败了,令人惊讶的是,没有记录任何错误。我猜这是因为 timestamp 解析为 DateTime 不适用于 Jdbc。

如果可以继承并重写 BeanPropertyRowMapper 并指示将所有 java.sql.Timestamp 和 java.sql.Date 转换为 DateTime ,那就太好了,可以节省很多额外的代码。

有什么建议吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

鼻尖触碰 2024-12-16 10:43:36

正确的做法是子类化 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());
    }
}

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());
    }
}
森末i 2024-12-16 10:43:36

查看 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 Roperty d escriptor”,用于 ( pd.getPropertyType() ) JdbcUtils 作为要映射到的字段的类型

如果您查看 getResultSetValue 方法的 JdbcUtils 代码,您会发现它只是从一个 if 语句转到另一个 if 语句,以匹配 >pd.getPropertyType() 到所有标准类型。当它找不到时,由于DateTime不是“标准”类型,它依赖于rs.getObject()

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

那么如果这个对象是一个SQL Date它将其转换为 Timestamp,并返回以设置为您域的 DateTime 字段 =>失败的地方。

因此,似乎没有一种直接的方法可以将 Date/TimestampDateTime 转换器注入 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) {}

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) {}
倾城花音 2024-12-16 10:43:36

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

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;
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文