IllegalStateException:无法将类型'java.sql.Timestamp'的值转换为属性所需的类型'java.time.LocalDateTime'

时间:2018-06-16 11:49:10

标签: spring spring-boot spring-jdbc

我正在开发一个spring-boot / jpa / mysql项目。到目前为止,当使用存储库获取/存储对象时,一切都与DateTime对象一起工作。

当我使用Jdbc模板执行自定义sql查询时,现在出现了问题。

org.springframework.beans.ConversionNotSupportedException: Failed to convert property 
  value of type 'java.sql.Timestamp' to required type java.time.LocalDateTime' for 
  property 'from_time': no matching editors or conversion strategy found

我们的想法是获取与新传入条目重叠的时隙(具有以分钟为单位的开始时间和持续时间)。

为了取回我的对象​​,我首先使用BeanPropertyMapper,然后切换到自定义的NestedRowMapper。 我希望得到的冲突时间段如下所示:

{
   id: 1
   comment: "i worked 60minutes"
   from_time: "2018-06-16 13:00"
   duration_minutes: 60
   task: {
      name: "My task"
      ...
   }
}

这是我遇到问题的方法:

public List<TimeSlot> getOverlappingEntries(TimeSlot timeslot) throws SQLException {
        String sql = "SELECT time_slot.comment, time_slot.from_time,"
            + "DATE_ADD(from_time, INTERVAL duration_minutes MINUTE) AS end_time, "
            + " task.name as `task.name`, task.category as `task.category` "
            + " FROM `time_slot` " + " INNER JOIN task on task.id = time_slot.task_id "
            + " WHERE person_id = ? "
            + " HAVING ? < end_time AND DATE_ADD(? ,INTERVAL ? MINUTE) > from_time;";
        PreparedStatementCreator prepared = (con) -> {
            PreparedStatement prep = con.prepareStatement(sql);
            prep.setObject(1, timeslot.person.id);
            prep.setObject(2, timeslot.from_time);
            prep.setObject(3, timeslot.from_time);
            prep.setObject(4, timeslot.durationMinutes);
            logger.info(prep.toString());
            return prep;
        };
        return this.connector.query(prepared, NestedRowMapper.get(TimeSlot.class));
    }

现在我想象春天能够轻松转换这些物体。无论如何,有timestamp.toLocalDateTime()这样做的简单方法。问题似乎更多如何将其注册为转换器服务或如何修复spring-boot配置。

我已经尝试过自定义转换器服务,但这没有帮助:

@javax.persistence.Converter
public class SqlTimestampToLocalDateTimeConverter implements Converter<Timestamp, 
        LocalDateTime>, AttributeConverter<Timestamp, LocalDateTime> {

    @Convert
    @Override
    public LocalDateTime convert(Timestamp source) {
        return source.toLocalDateTime();
    }

    @Override
    public LocalDateTime convertToDatabaseColumn(Timestamp attribute) {
        return attribute.toLocalDateTime();
    }

    @Override
    public Timestamp convertToEntityAttribute(LocalDateTime dbData) {
        return Timestamp.valueOf(dbData);
    }
}

互联网上的许多其他答案都提到这已经用spring framework 4.x实现了。 项目中的依赖项如下所示(build.gradle):

dependencies {
    compile "org.springframework.boot:spring-boot-starter-thymeleaf:2.0.2.RELEASE"
    compile "org.springframework.boot:spring-boot-starter-web:2.0.2.RELEASE"
    compile "org.springframework.boot:spring-boot-starter-security:2.0.2.RELEASE"
    compile "org.springframework.boot:spring-boot-starter-data-jpa:2.0.2.RELEASE"
    compile "mysql:mysql-connector-java:5.1.46"
    compileOnly "org.springframework.boot:spring-boot-devtools:2.0.2.RELEASE"
    compile 'org.springframework.data:spring-data-rest-webmvc:3.0.7.RELEASE'
    compile 'com.querydsl:querydsl-jpa:4.1.4'
    compile 'com.querydsl:querydsl-apt:4.1.4:jpa'

    testCompile("junit:junit")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    testCompile("org.springframework.security:spring-security-test")
}

感谢您提供任何提示,如何解决此问题!

/编辑:

我想我现在看到了可能的解决方法。我能做的只是获取所有时隙的id,然后使用存储库来获取带有数据的实际对象(也包括它们的任务对象)。 但这感觉绝对不是最佳解决方案......

这是我使用的NestedRowMapper:

import org.springframework.beans.*;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.JdbcUtils;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

public class NestedRowMapper<T> implements RowMapper<T> {

    private Class<T> mappedClass;

    public static <T> NestedRowMapper<T> get(Class<T> mappedClass) {
        return new NestedRowMapper<>(mappedClass);
    }

    public NestedRowMapper(Class<T> mappedClass) {
        this.mappedClass = mappedClass;
    }

    @Override
    public T mapRow(ResultSet rs, int rowNum) throws SQLException {
        try {
            T mappedObject = this.mappedClass.newInstance();;
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);

            bw.setAutoGrowNestedPaths(true);

            ResultSetMetaData meta_data = rs.getMetaData();
            int columnCount = meta_data.getColumnCount();

            for (int index = 1; index <= columnCount; index++) {

                try {

                    String column = JdbcUtils.lookupColumnName(meta_data, index);
                    Object value = JdbcUtils.getResultSetValue(rs, index, Class.forName(meta_data
                        .getColumnClassName(index)));

                    bw.setPropertyValue(column, value);

                } catch (TypeMismatchException | NotWritablePropertyException
                    | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

            return mappedObject;
        } catch (InstantiationException | IllegalAccessException e1) {
            throw new RuntimeException(e1);
        }
    }
}

2 个答案:

答案 0 :(得分:0)

位于右边的行中,您可以定义RowMapper,该值告诉您的应用每列需要映射到哪种对象。我建议尝试使用JdbcTemplate.query方法:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html#query-java.lang.String-java.lang.Object:A-org.springframework.jdbc.core.RowMapper-

您将需要定义一个RowMapper(不一定是NestedRowMapper,您可以尝试ParameterizedRowMapper),然后使用SQL和{{1 }}条件映射为query

答案 1 :(得分:0)

我认为在您的getOverlappingEntries方法中使用BeanPropertyRowMapper.newInstance(TimeSlot.class)的最明智的方法