我使用的是Postgres SQL 9.2、版本为4.0.5的Spring JDBC和Java 8
Java8引入了新的日期/时间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);
}
当然我也有例外:
...
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包装器添加了转换服务,它能够执行从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,因为它更方便,并且能够将其持久化到数据库。我希望它应该在默认情况下启用。
新日期&JEP170:JDBC4.2定义了API对JDBC的支持。Postgres下载页面与JDBC4.2的兼容性新功能仅从Postgres 9.4版本开始,因此使用新的API和旧的驱动程序会出现一些兼容性挑战。
即使是setObject(1, new java.util.Date());
也被Postgres中的相同约束所拒绝(MySQL很乐意接受),而不仅仅是像LocalDate
这样的新API。有些行为将依赖于实现,所以(粗略地说)只有java.sql.*
是可以保证的。
至于Spring JDBC框架,我认为重写它的行为可以绕过它,以后不会后悔。对于你已经做的事情,我建议一个稍微不同的方法:
- 扩展CCD_ 4行为以使用新日期&时间API以及与参数输入相关联的其他类(如果需要的话)(我不熟悉那个Spring API)
- 将
BeanPropertyRowMapper
已经被覆盖的行为提取到另一个类中进行获取操作 - 用工厂模式或实用程序类将其全部封装起来,这样您就不必再看它了
这样,如果API得到支持,就可以增强未来的重构功能,并减少开发过程中所需的代码量。
您还可以查看一些DAO方法。
请注意,支持Java 8日期和时间API(JSR-310),但实现尚未完成:https://jdbc.postgresql.org/documentation/head/8-date-time.html报价:
Note that ZonedDateTime, Instant and OffsetTime / TIME [ WITHOUT TIMEZONE ] are not supported.