我正在使用 Postgres SQL 9.2、版本为 4.0.5 的 Spring JDBC 和 Java 8。
Java 8 引入了新的日期/时间 API,我想使用它,但遇到了一些困难。
我创建了表 TABLE_A:
CREATE TABLE "TABLE_A"
(
new_date date,
old_date date
)
我正在使用 Spring JDBC 与数据库进行通信。我创建了 Java 类,对应于此表:
public class TableA
{
private LocalDate newDate;
private Date oldDate;
//getters and setters
}
这是我负责插入新行的代码:
public void create(TableA tableA)
{
BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(tableA);
final String sql = "INSERT INTO public.TABLE_A (new_date,old_date) values(:newDate,:oldDate)";
namedJdbcTemplate.update(sql,parameterSource);
}
当我执行此方法时出现异常:
org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of java.time.LocalDate. Use setObject() with an explicit Types value to specify the type to use.
所以我更新了 BeanPropertySqlParameterSource 的创建:
BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(tableA);
parameterSource.registerSqlType("newDate", Types.DATE);
在那次更改之后,我能够插入行。但接下来,我想从数据库中获取行。这是我的方法:
public List<TableA> getAll()
{
final String sql = "select * from public.TABLE_A";
final BeanPropertyRowMapper<TableA> rowMapper = new BeanPropertyRowMapper<TableA>(TableA.class);
return namedJdbcTemplate.query(sql,rowMapper);
}
当然我得到了异常(exception):
...
at org.springframework.beans.BeanWrapperImpl.convertIfNecessary(BeanWrapperImpl.java:474)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:511)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1119)
at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:902)
at org.springframework.jdbc.core.BeanPropertyRowMapper.mapRow(BeanPropertyRowMapper.java:255)
...
Caused by: java.lang.IllegalStateException: Cannot convert value of type [java.sql.Date] to required type [java.time.LocalDate] for property 'newDate': no matching editors or conversion strategy found.
所以我更新了我的代码,这次是BeanPropertyRowMapper,我为bean wrapper添加了转换服务,它能够执行从java.sql.Date到java.time.LocalDate的转换
public List<TableA> getAll()
{
final String sql = "select * from public.TABLE_A";
final BeanPropertyRowMapper<TableA> rowMapper = new BeanPropertyRowMapper<TableA>(TableA.class)
{
@Override
protected void initBeanWrapper(BeanWrapper bw) {
super.initBeanWrapper(bw);
bw.setConversionService(new ConversionService() {
@Override
public boolean canConvert(Class<?> aClass, Class<?> aClass2) {
return aClass == java.sql.Date.class && aClass2 == LocalDate.class;
}
@Override
public boolean canConvert(TypeDescriptor typeDescriptor, TypeDescriptor typeDescriptor2) {
return canConvert(typeDescriptor.getType(), typeDescriptor2.getType());
}
@Override
public <T> T convert(Object o, Class<T> tClass) {
if(o instanceof Date && tClass == LocalDate.class)
{
return (T)((Date)o).toLocalDate();
}
return null;
}
@Override
public Object convert(Object o, TypeDescriptor typeDescriptor, TypeDescriptor typeDescriptor2) {
return convert(o,typeDescriptor2.getType());
}
});
}
} ;
return namedJdbcTemplate.query(sql,rowMapper);
现在一切正常,但它相当复杂。
实现这一目标的方法更简单吗?
一般来说,我想在我的Java代码中对LocalDate进行操作,因为这样更方便,并且能够持久化到数据库。我希望它应该默认启用。
最佳答案
JDBC 的新日期和日期 API 支持由 JEP 170: JDBC 4.2 定义. Postgres download page与 JDBC 4.2 新功能的兼容性仅从 Postgres 9.4 版开始,因此将新 API 与旧驱动程序一起使用会出现一些兼容性挑战。
甚至 setObject(1, new java.util.Date());
也被 Postgres 中的相同约束拒绝(这很高兴被 MySQL 接受),不仅仅是像 本地日期
。一些行为将依赖于实现,因此只有 java.sql.*
几乎可以保证(粗略地说)。
至于 Spring JDBC 框架,我认为覆盖它的行为可以绕过它而不会后悔。对于您已经做过的事情,我建议采用稍微不同的方法:
- 扩展
BeanPropertySqlParameterSource
行为以使用新的日期和时间 API,以及在需要时与参数输入关联的其他类(我不熟悉那个 Spring API)。 - 将
BeanPropertyRowMapper
已经覆盖的行为提取到另一个类以进行获取操作。 - 用工厂模式或实用程序类将其全部包装起来,这样您就不必再看一遍。
如果 API 得到支持,您可以通过这种方式增强 future 的重构能力,并减少开发过程中所需的代码量。
您还可以查看一些 DAO方法。
关于java - Spring JDBC + Postgres SQL + Java 8 - 从/到 LocalDate 的转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25536969/