Spring JDBC + Postgres SQL + Java 8 - 从/到LocalDate的转换

Ada*_*wka 8 java postgresql spring spring-jdbc java-8

我正在使用Postgres SQL 9.2,Spring JDBC版本4.0.5和Java 8.
Java 8引入了新的日期/时间API,我想使用它,但是我遇到了一些困难.我创建了表TABLE_A:

CREATE TABLE "TABLE_A"
(
  new_date date,
  old_date date
)
Run Code Online (Sandbox Code Playgroud)

我正在使用Spring JDBC与数据库进行通信.我创建了Java类,它对应于这个表:

public class TableA
{
    private LocalDate newDate;
    private Date oldDate;
    //getters and setters

}
Run Code Online (Sandbox Code Playgroud)

这是我的代码,它可以插入新行:

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

}
Run Code Online (Sandbox Code Playgroud)

当我执行这个方法时,我得到了异常:

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.
Run Code Online (Sandbox Code Playgroud)

所以我更新了BeanPropertySqlParameterSource的创建:

BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(tableA);
parameterSource.registerSqlType("newDate", Types.DATE); 
Run Code Online (Sandbox Code Playgroud)

在那次改变后,我能够插入行.但接下来,我想从数据库中获取行.这是我的方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

当然我得到例外:

...
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.
Run Code Online (Sandbox Code Playgroud)

所以我更新了我的代码,这次是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);
Run Code Online (Sandbox Code Playgroud)

现在一切正常,但它很复杂.
是否更容易实现这一目标?一般来说,我想在我的Java代码中使用LocalDate,因为它更方便,并且能够将它持久化到数据库.我希望默认情况下应该启用它.

Eva*_*tti 7

使用JDBC的新日期和日期API支持由JEP 170:JDBC 4.2定义.Postgres 下载页面与JDBC的兼容性4.2新功能仅从Postgres版本9.4开始,因此使用旧版驱动程序的新API会弹出一些兼容性挑战.

甚至setObject(1, new java.util.Date());被Postgres中的相同约束拒绝(MySQL很乐意接受),而不仅仅是新的API LocalDate.一些行为将依赖于实现,因此只能java.sql.*保证很多(粗略地说).


对于Spring JDBC框架,我认为重写它的行为可以解决它,而不会后悔.对于你已经做过的事情,我建议采用略有不同的方法:

  1. 扩展BeanPropertySqlParameterSource行为以使用新的日期和时间API,以及与参数输入相关的其他类(如果需要)(我不熟悉Spring API).
  2. 将已经被覆盖的行为提取BeanPropertyRowMapper到另一个类以获取操作.
  3. 使用工厂模式或实用程序类将其全部包装起来,这样您就不必再次查看它了.

这样,如果API得到支持,您可以增强未来的重构功能,并减少开发期间所需的代码量.

您还可以查看一些DAO方法.

  • 我修复了我的* org.postgresql.util.PSQLException:不能推断SQL类型用于java.time.LocalDate的实例。将setObject()与显式的Types值一起使用以指定要使用的类型,以将我的postgres JDBC从* 9.4 Build 1206 *更新为* 9.4.1212 *版本 (2认同)