Spring Boot/JPA REST API控制器传递日期异常:无法转换PathVariable的java.sql.Date类型
这个问题我之前也碰到过,核心原因是Spring无法自动在java.util.Date和java.sql.Date之间完成转换,再加上旧日期API的类型混淆,就抛出了转换器找不到的错误。下面给你几个可行的解决方案,优先推荐使用Java 8+的新日期API,因为它能彻底避免这类类型混乱问题。
方案1:改用Java 8+的日期时间API(推荐)
旧的Date类(不管是util还是sql包下的)设计缺陷很多,Java 8引入的LocalDate、LocalDateTime是更安全、更清晰的选择,而且Spring和JPA对它们的支持非常完善。
步骤1:修改控制器参数类型
把控制器中的Date替换为LocalDate,并保留@DateTimeFormat注解:
import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @GetMapping(path = "/{startDate}/{endDate}") public List<CarResponse> getCarsSoldBetween( @PathVariable("startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate, @PathVariable("endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) { // 把LocalDate转换为包含时间范围的LocalDateTime,确保覆盖当天所有时间点 LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); List<CarResponse> cars = myRepository.getCarsSoldBetween(startDateTime, endDateTime); return cars; }
步骤2:修改Repository方法
因为你的soldDate是DB2的TIMESTAMP类型,对应实体类的字段应该是LocalDateTime,所以Repository的参数也要改成LocalDateTime:
import java.time.LocalDateTime; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @Query("select c from CarEntity c where c.carType = 'SPORT' and c.soldDate between ?1 and ?2") List<CarEntity> getCarsSoldBetween(LocalDateTime startDateTime, LocalDateTime endDateTime);
方案2:统一使用java.sql.Date(兼容旧代码)
如果你暂时不想切换到新API,那要确保控制器和Repository中的日期类型完全一致,避免util.Date和sql.Date混用。
步骤1:修改控制器参数为java.sql.Date
明确指定参数类型为java.sql.Date,Spring就能直接将路径变量的字符串转换为该类型:
import java.sql.Date; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @GetMapping(path = "/{startDate}/{endDate}") public List<CarResponse> getCarsSoldBetween( @PathVariable("startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @PathVariable("endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) { // 注意:java.sql.Date会截断时间,所以要把结束日期加1天,确保包含当天所有时间 Date endDatePlusOne = Date.valueOf(endDate.toLocalDate().plusDays(1)); List<CarResponse> cars = myRepository.getCarsSoldBetween(startDate, endDatePlusOne); return cars; }
步骤2:修改Repository参数为java.sql.Date
确保Repository的参数和控制器一致,同时调整查询逻辑适配时间截断问题:
import java.sql.Date; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @Query("select c from CarEntity c where c.carType = 'SPORT' and c.soldDate >= ?1 and c.soldDate < ?2") List<CarEntity> getCarsSoldBetween(Date startDate, Date endDatePlusOne);
这里把between改成>=和<,是因为java.sql.Date对应的是当天00:00:00,加1天后的日期就能覆盖原结束日的所有时间点。
方案3:自定义转换器(灵活适配)
如果需要更灵活的转换逻辑,可以自定义一个Spring转换器,让它直接将字符串路径变量转换为java.sql.Date:
import java.sql.Date; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; @Component public class StringToSqlDateConverter implements Converter<String, Date> { private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); @Override public Date convert(String source) { if (source == null || source.isBlank()) { return null; } LocalDate localDate = LocalDate.parse(source, DATE_FORMAT); return Date.valueOf(localDate); } }
注册这个转换器后,控制器参数可以直接用java.sql.Date,甚至可以去掉@DateTimeFormat注解(如果格式固定的话)。
内容的提问来源于stack exchange,提问作者pixel

