java - Spring data JPA 检索两个日期之间的数据不起作用

标签 java mysql spring hibernate jpa

编辑:

我试图通过按日期过滤数据来获取数据,但出现以下错误。我接受 LocalDateTimeFormat 中的日期。但是,在 JPA 实体中 created_date 的类型是 Instant。我无法更改实体中的类型,因为它可能会破坏其他 API

Parameter value [2021-12-23T13:00] did not match expected type [java.time.Instant (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [2021-12-23T13:00] did not match expected type [java.time.Instant (n/a)]

curl 请求

curl --location --request GET 'http://localhost:9202/movement-history?amd=xs&fromCreatedDate=2021-12-23T13:00&toCreatedDate=2021-12-23T15:10'

回应

{
    "code": 0,
    "message": "Success",
    "data": {
        "movementHistory": [],
        "totalCount": 0,
        "page": 1,
        "limit": 20
    }
}

Controller

@GetMapping( value = "/movement-history")
    public ResponseDto<RetrieveMovementHistoryResponse> retrieveMovementHistory(
            @Valid RetrieveMovementHistoryRequest retrieveMovementHistoryRequest
    ) {
        return responseUtil.prepareSuccessResponse( retrieveHistoryService.doAction( retrieveMovementHistoryRequest ),
                null );
    }

检索MovementHistoryRequest DTO

package ai.growing.platform.inventory.dto;

import ai.growing.platform.library.dto.Request;
import lombok.*;
import lombok.experimental.FieldDefaults;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.time.Instant;
import java.util.List;

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldDefaults( level = AccessLevel.PRIVATE )
public class RetrieveMovementHistoryRequest extends Request {
    List<String> amd;
    @DateTimeFormat( pattern = "yyyy-MM-dd'T'HH:mm" )
    LocalDateTime fromCreatedDate;
    @DateTimeFormat( pattern = "yyyy-MM-dd'T'HH:mm" )
    LocalDateTime toCreatedDate;
    @Positive( message = "limit can not be negative" )
    @Builder.Default
    @NotNull
    Integer limit = 20;

    @Builder.Default
    @Range( min = 1, message = "page can not be zero or negative" )
    @NotNull
    Integer page = 1;
}

JPA 搜索规范

public class TransactionSearchSpecification {

    private static final String AMD = "amd";
    private static final String CREATED_DATE = "createdDate";

    public static Specification<Transaction> getTransactionBySpecification(
        TransactionSearchCriteria transactionSearchCriteria) {

        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if( !CollectionUtils.isEmpty( transactionSearchCriteria.getAmdList() ) ) {
                predicates.add( root.get( SKU ).in( transactionSearchCriteria.getAmdList() ) );
            }

            if( !StringUtils.isEmpty( transactionSearchCriteria.getFromDate() ) && !StringUtils.isEmpty(
                transactionSearchCriteria.getToDate() ) ) {
                predicates.add( criteriaBuilder
                    .between( root.get( CREATED_DATE )
                        , transactionSearchCriteria.getFromDate(), transactionSearchCriteria.getToDate() ) );
            }

           
            return criteriaBuilder.and( predicates.toArray( new Predicate[ 0 ] ) );
        };
    }
}

实体

package ai.growing.platform.product.inventory.entity;

import java.io.Serializable;
import java.time.Instant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@Builder
@Entity
@Table( name = "transaction" )
@Data
@NoArgsConstructor
public class Transaction implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue( strategy = GenerationType.IDENTITY )
    @Column( name = "id", nullable = false )
    private Long id;

    @Column( name = "amd", nullable = false )
    private String amd;
    
    @Column( name = "request_type" )
    private String requestType;

    @CreationTimestamp
    @Column( name = "created_date", nullable = false )
    private Instant createdDate;
    
}

数据库中的数据

id  created_date    request_type    amd
850476  2021-12-23 13:01:19 COMPLETED   xs
850480  2021-12-23 14:58:17 COMPLETED   xs
850474  2021-12-23 13:00:41 INITIATED   xs
850478  2021-12-23 14:58:08 INITIATED   xs

JPA发出的查询

select
        transactio0_.id as id1_11_,
        transactio0_.created_date as created_2_11_,
        transactio0_.amd as amd3_11_,
    from
        transaction transactio0_ 
    where
        (
            transactio0_.amd in (
                ?
            )
        ) 
        and (
            transactio0_.created_date between ? and ?
        ) limit ?
2022-01-24 09:02:00.918 TRACE [...true] 29314 --- [tp1324897979-85] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [xs]
2022-01-24 09:02:00.918 TRACE [...,true] 29314 --- [tp1324897979-85] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [TIMESTAMP] - [2021-12-23T13:00:00Z]
2022-01-24 09:02:00.920 TRACE [...,true] 29314 --- [tp1324897979-85] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [TIMESTAMP] - [2021-12-23T15:10:00Z]

最佳答案

正如您在堆栈跟踪中看到的,该错误与您提供的值 2021-12-23T13:00Instant 之间的转换有关,可能是在 Spring 时Data 执行您在规范中创建的 JPA Criteria 查询。

要解决此问题,您可以尝试在 Specification 代码中手动从 LocalDateTime 转换为 Instant - 我假设 transactionSearchCriteria .getFromDate()transactionSearchCriteria.getToDate()String。例如:

public class TransactionSearchSpecification {

    private static final String AMD = "amd";
    private static final String CREATED_DATE = "createdDate";

    public static Specification<Transaction> getTransactionBySpecification(
        TransactionSearchCriteria transactionSearchCriteria) {

        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if( !CollectionUtils.isEmpty( transactionSearchCriteria.getAmdList() ) ) {
                predicates.add( root.get( SKU ).in( transactionSearchCriteria.getAmdList() ) );
            }

            String fromDate = transactionSearchCriteria.getFromDate();
            String toDate = transactionSearchCriteria.getToDate();
            if( !StringUtils.isEmpty( fromDate ) && !StringUtils.isEmpty(
                toDate ) {
                try {
                  Instant fromInstant = this.fromString(fromDate);
                  Instant toInstant = this.fromString(toDate);
                  predicates.add(
                    criteriaBuilder.between(
                      root.get( CREATED_DATE ), fromInstant, toInstant
                    )
                  );
                } catch (DateTimeParseException dtpe) {
                  // invalid format, consider log the error, etcetera
                  dtpe.printStackTrace();
                }
            }
           
            return criteriaBuilder.and( predicates.toArray( new Predicate[ 0 ] ) );
        };
    }

    // This method is suitable for being defined probably in a common, utility class
    // Following, for example, the advice provided in this SO question
    // https://stackoverflow.com/questions/50299639/converting-string-to-instant
    private Instant fromString(final String localDateTimeString) throws DateTimeParseException {
      if (StringUtils.isEmpty(localDateTimeString) {
        return null;
      }

      // Convert String to LocalDateTime
      LocalDateTime localDateTime = LocalDateTime.parse(
        localDateTimeString,
        DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm" , Locale.US )
      );

      // Provide the appropriate ZoneId
      Instant result = localDateTime
        .atZone(ZoneId.of("Asia/Kolkata"))
        .toInstant();

      // Return the obtained instant
      result;
    }
}

我不清楚的是 RetrieveProductMovementHistoryRequest 的作用。如果规范以任何方式涉及使用LocalDateTime fromCreatedDate,那么我的建议是您需要手动调整规范中的代码 来处理Instant 值。我的意思是:

public class TransactionSearchSpecification {

    private static final String AMD = "amd";
    private static final String CREATED_DATE = "createdDate";

    public static Specification<Transaction> getTransactionBySpecification(
        TransactionSearchCriteria transactionSearchCriteria) {

        return (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            if( !CollectionUtils.isEmpty( transactionSearchCriteria.getAmdList() ) ) {
                predicates.add( root.get( SKU ).in( transactionSearchCriteria.getAmdList() ) );
            }

            LocalDateTime fromDate = transactionSearchCriteria.getFromDate();
            LocalDateTime toDate = transactionSearchCriteria.getToDate();
            if( fromDate != null && toDate != null ) {
                Instant fromInstant = this.fromLocalDateTime(fromDate);
                Instant toInstant = this.fromLocalDateTime(toDate);
                predicates.add(
                  criteriaBuilder.between(
                    root.get( CREATED_DATE ), fromInstant, toInstant
                  )
                );
            }

           
            return criteriaBuilder.and( predicates.toArray( new Predicate[ 0 ] ) );
        };
    }

    // Again, this method is suitable for being defined probably in a common, utility class
    private Instant fromLocalDateTime(final LocalDateTime localDateTime) {
      if (localDateTime == null) {
        return null;
      }

      // Provide the appropriate ZoneId
      Instant result = localDateTime
        .atZone(ZoneId.of("Asia/Kolkata"))
        .toInstant();

      // Return the obtained instant
      result;
    }
}

对于您的评论,您似乎没有得到任何结果。

第一步,尝试调试并隔离错误:请务必检查您的存储库是否实际返回任何结果。

如果它从数据库返回适当的记录,请检查您的服务代码,即与规范交互的服务代码。

如果您的存储库没有返回任何结果,则问题可能与不同的事情有关。

请注意,我们主要使用的是时间点。正如您可以在 MySql documentation 中读到的那样,根据不同的因素,尤其是 CREATED_DATE 列的类型(TIMESTAMPDATETIME),您可能会在以下情况下获得意外结果:执行查询,这可以解释为什么您没有获得任何结果。

关于java - Spring data JPA 检索两个日期之间的数据不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70802115/

相关文章:

java - 从 Java/Swing 应用程序使用 Mac OS X 服务菜单

java - 使用大整数时间复杂度的递归斐波那契

java - org.testng.TestNGException : An error occurred while instantiating class. 检查以确保它可以被实例化/访问

mysql - 配置单元 mysql 连接器错误

mysql - 删除MySQL日志文件会影响数据库的性能吗?

spring - 如何启用 'ALLOW_NUMERIC_LEADING_ZEROS' 功能以允许在 JSON 请求正文中使用前导零?

java - Tomcat 服务器正在启动,但当我打开它时只显示空白页面

java - 建议Windows环境下的Java Web Host

spring - java.lang.OutOfMemoryError : PermGen space with tomcat7 windows service 错误

java - 一台 Apache Tomcat 应用程序服务器,带有 2 个已配置的 war 应用程序和一个 MessageSource 异常