【问题标题】:Why does Spring Data give me this reflection exception?为什么 Spring Data 会给我这个反射异常?
【发布时间】:2017-03-25 23:39:37
【问题描述】:

我有一些实体、存储库和谓词来处理动态查询生成器。当我运行一个简单的查询时,我得到一个异常。

  1. 为什么会出现反射错误?
  2. 在跟踪的底部附近,它说它不能将 Integer 设置为 String。我不知道为什么会在那里,或者它是否相关。无论如何,所有值都是整数,所以我什至不知道字符串来自哪里。
  3. 异常发生在对 findAll() 的调用中。我假设 Spring Data 工作正常并且我配置错误。我只是看不出它是什么。

StackTrace(已编辑以减小帖子大小)

    2016-11-11 11:52:05,405 [http-bio-8080-exec-13] ERROR com.etisoftware.lib.spring.web.controller.GlobalExceptionHandlerAdvice:34 - An uncaught exception occurred
    org.springframework.orm.jpa.JpaSystemException: Error accessing field [private java.lang.Integer com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner.number] by reflection for persistent property [com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner#number] : 1; nested exception is org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.Integer com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner.number] by reflection for persistent property [com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner#number] : 1
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:333)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)

    Caused by: org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.Integer com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner.number] by reflection for persistent property [com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner#number] : 1
        at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:71)
        at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:224)

        ... 89 more
    Caused by: java.lang.IllegalArgumentException: Can not set java.lang.Integer field com.etisoftware.workorderprinting.beans.cborg.WorkOrderOwner.number to java.lang.String
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
        at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
        at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
        at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36)
        at java.lang.reflect.Field.get(Field.java:393)
        at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:67)
        ... 133 more        

工单实体

@Entity
@Table(name = "workorders")
public class WorkOrder
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "workorder_nbr")
  private Integer           number;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "subscriber_nbr")
  private Subscriber        subscriber;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "wo_type")
  private WorkOrderType     type;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "pool_nbr")
  private WorkerPool        pool;

  @JoinColumn(name = "wo_status_nbr")
  @ManyToOne(fetch = FetchType.LAZY)
  private WorkOrderStatus   status;

  @JoinColumn(name = "wo_owner_nbr")
  @ManyToOne(fetch = FetchType.LAZY)
  private WorkOrderOwner    owner;

  @Column(name = "sched_date")
  private LocalDate         scheduleDate;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "slot_nbr")
  private TimeSlot slot;

  @Column(name = "auto_complete_date")
  private LocalDate         autoCompleteDate;

  @Column(name = "override")
  private Boolean           override;

  @JoinColumn(name = "wo_location_nbr")
  @ManyToOne(fetch = FetchType.LAZY)
  private Location          location;

  /*
   * Fetch the notes later in the service using this number because connecting the notes table is a pain
   */
  @Column(name = "wo_note_nbr")
  private Integer           noteNumber;

WorkOrderOwner 实体

@Entity
@Table(name = "wo_owners")
public class WorkOrderOwner
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "wo_owner_nbr")
  private Integer number;

  @Column(name = "owner_id")
  private String  name;

  @Column(name = "email_address")
  private String  email_address;

WorkOrderPredicates

@Component
public class WorkOrderPredicates
{
  private static DateTimeFormatter dateFormatter     = DateTimeFormatter.ofPattern("MM/dd/yyyy");
  private static DateTimeFormatter timeFormatter     = DateTimeFormatter.ofPattern("hh:mm:ss");
  private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm:ss");

  public Predicate buildQueryFromSearchCriteria(PrintWorkOrderFormDto queryDto)
  {

    BooleanBuilder builder = new BooleanBuilder();
    List<Predicate> predicates = new ArrayList<>();
    /*
     * TODO I think think this string "entity" which is a variable name is going to give us a problem.
     */
    PathBuilder<WorkOrder> entityPath = new PathBuilder<>(WorkOrder.class, "workOrder");

    for (SelectedCriteria criteria : queryDto.getCriteria().getRules())
    {
      assignValueAndOperator(predicates, entityPath, criteria);
    }

    /*
     * Per Chris, the queries will only have one group. That is, there will be a single condition [AND | OR] and then a
     * list of criteria.
     */
    if (queryDto.getCriteria().getCondition().equals(SelectionFilterCondition.AND))
    {
      for (Predicate p : predicates)
        builder.and(p);
    }
    else if (queryDto.getCriteria().getCondition().equals(SelectionFilterCondition.OR))
    {
      for (Predicate p : predicates)
        builder.or(p);
    }

    return builder;
  }

  @SuppressWarnings("unchecked")
  protected static void assignValueAndOperator(List<Predicate> predicates, PathBuilder<?> entityPath, SelectedCriteria criteria)
  {
    switch (criteria.getOperator())
    {
      case BETWEEN:
        List<LocalDate> values = (List<LocalDate>) criteria.getValue();
        LocalDate from = values.get(0);
        LocalDate to = values.get(1);
        // TODO how do we handle Time separately from Date.
        // TODO how do we handle DateTime
        predicates.add(entityPath.getDate(criteria.getField(), LocalDate.class).between(from, to));
        break;
      case BEGINS_WITH:
        predicates.add(entityPath.getString(criteria.getField()).like(criteria.getValue() + "%"));
        break;
      case NOT_BEGINS_WITH:
        predicates.add(entityPath.getString(criteria.getField()).notLike(criteria.getValue() + "%"));
        break;
      case CONTAINS:
        predicates.add(entityPath.getString(criteria.getField()).like("%" + criteria.getValue() + "%"));
        break;
      case NOT_CONTAINS:
        predicates.add(entityPath.getString(criteria.getField()).notLike("%" + criteria.getValue() + "%"));
        break;
      case ENDS_WITH:
        predicates.add(entityPath.getString(criteria.getField()).like("%" + criteria.getValue()));
        break;
      case NOT_ENDS_WITH:
        predicates.add(entityPath.getString(criteria.getField()).notLike("%" + criteria.getValue()));
        break;
      case EQUAL:
        predicates.add(entityPath.get(criteria.getField()).eq(extractAndCreateValueOfType(criteria.getValue(), criteria.getType())));
        break;
      case NOT_EQUAL:
        predicates.add(entityPath.get(criteria.getField()).ne(criteria.getValue()));
        break;
      case GREATER:
        // TODO this casting might not be right
        predicates.add(entityPath.getNumber(criteria.getField(), Integer.class).gt((Integer) criteria.getValue()));
        break;
      case LESS:
        // TODO this casting might not be right
        predicates.add(entityPath.getNumber(criteria.getField(), Integer.class).lt((Integer) criteria.getValue()));
        break;
      case LESS_OR_EQUAL:
        // TODO this casting might not be right
        predicates.add(entityPath.getNumber(criteria.getField(), Integer.class).loe((Integer) criteria.getValue()));
        break;
      case GREATER_OR_EQUAL:
        // TODO this casting might not be right
        predicates.add(entityPath.getNumber(criteria.getField(), Integer.class).goe((Integer) criteria.getValue()));
        break;
      case IN:
        /*
         * TODO this is causing a hibernate error because its trying to map an integer to a string. This type setting
         * below needs to be dynamic based on the selectedcriteria type
         */
        List<String> inValues = (List<String>) criteria.getValue();
        predicates.add(entityPath.get(criteria.getField(), String.class).in(inValues));
        break;
      case NOT_IN:
        List<String> notInValues = (List<String>) criteria.getValue();
        predicates.add(entityPath.get(criteria.getField(), String.class).in(notInValues).not());
        break;
      case IS_EMPTY:
        predicates.add(entityPath.getList(criteria.getField(), List.class).isEmpty());
        break;
      case IS_NOT_EMPTY:
        predicates.add(entityPath.getList(criteria.getField(), List.class).isNotEmpty());
        break;
      case IS_NOT_NULL:
        predicates.add(entityPath.get(criteria.getField()).isNotNull());
        break;
      case IS_NULL:
        predicates.add(entityPath.get(criteria.getField()).isNull());
        break;
      default:
        // TODO implement the default case
        break;
    }

  }

  @SuppressWarnings("cast")
  private static Object extractAndCreateValueOfType(Object valueObject, SelectionDataType selectionDataType)
  {
    Object value = null;
    switch (selectionDataType)
    {
      case BOOLEAN:
        // TODO
        break;
      case DATE:
        value = LocalDate.parse((String) valueObject, dateFormatter);
        break;
      case TIME:
        value = LocalDateTime.parse((String) valueObject, timeFormatter);
        break;
      case DATETIME:
        value = LocalDateTime.parse((String) valueObject, dateTimeFormatter);
        break;
      case DOUBLE:
        value = (Double) valueObject;
        break;
      case INTEGER:
        value = (Integer) valueObject;
        break;
      case STRING:
        value = (String) valueObject;
        break;
      default:
        break;
    }
    return value;
  }

WorkOrderService 获取方法

 @Transactional(transactionManager = PersistenceConfigCbOrgAbstract.txMgrName, readOnly = true)
  protected List<WorkOrderDto> fetchWorkOrders(PrintWorkOrderFormDto queryParams, PrintStatus printStatus)
  {
    List<WorkOrderDto> dtos = new ArrayList<>();
    printStatus.setMessage("Fetching work orders");
    logger.debug("Query Params: " + queryParams);

    for (WorkOrder bean : workOrderRepository.findAll(workOrderPredicates.buildQueryFromSearchCriteria(queryParams)))
    {
      logger.debug(ToStringBuilder.reflectionToString(bean));
      WorkOrderDto dto = modelMapper.map(bean, WorkOrderDto.class);
      fetchAndAssignNotes(dto, bean.getNoteNumber());
      fetchAndAssignCreateDate(dto, bean.getNumber());
      fetchAndAssignPendingDeviceDtos(dto, bean.getNumber());
      fetchAndAssignPendingServiceDtos(dto, bean.getNumber());
      fetchAndAssignBilling(dto, bean.getNumber());

      try
      {
        dto.setCustomer(new CustomerDto());
        fetchAndAssignCustomer(dto);
      }
      catch (JsonParseException e)
      {
        logger.error("Could not parse customer information", e);
        printStatus.setMessage("Cannot fetch work orders. Please check the application log");
      }
      catch (JsonMappingException e)
      {
        logger.error("Could not map customer information", e);
        printStatus.setMessage("Cannot fetch work orders. Please check the application log");
      }
      catch (IOException e)
      {
        logger.error("Got an IO exception while trying to map customer information", e);
        printStatus.setMessage("Cannot fetch work orders. Please check the application log");
      }

      logger.debug(ToStringBuilder.reflectionToString(dto, ToStringStyle.MULTI_LINE_STYLE, Boolean.TRUE));
      printStatus.setRecordCount(printStatus.getRecordCount() + 1);
      dtos.add(dto);
    }

    return dtos;
  }

wo_owners 表

CREATE TABLE wo_owners (
  wo_owner_nbr serial NOT NULL,
  owner_id nchar(20) NOT NULL,
  email_address nchar(80),
  PRIMARY KEY (wo_owner_nbr)
);

工单

CREATE TABLE cborg2001:workorders (
  workorder_nbr serial NOT NULL,
  subscriber_nbr int,
  wo_type int,
  pool_nbr int,
  wo_status_nbr int,
  wo_owner_nbr int,
  wo_note_nbr int,
  sched_date date,
  slot_nbr int,
  auto_complete_date date,
  override int DEFAULT 0,
  wo_location_nbr int,
  PRIMARY KEY (workorder_nbr)
);

QueryDSL查询

com.querydsl.jpa.impl.JPAQuery:233 - select workOrder from WorkOrder workOrder where workOrder.scheduleDate = ?1

生成的查询

select
    workorder0_.workorder_nbr as workorde1_28_,
    workorder0_.auto_complete_date as auto_com2_28_,
    workorder0_.wo_location_nbr as wo_locat6_28_,
    workorder0_.wo_note_nbr as wo_note_3_28_,
    workorder0_.override as override4_28_,
    workorder0_.wo_owner_nbr as wo_owner7_28_,
    workorder0_.pool_nbr as pool_nbr8_28_,
    workorder0_.sched_date as sched_da5_28_,
    workorder0_.slot_nbr as slot_nbr9_28_,
    workorder0_.wo_status_nbr as wo_stat10_28_,
    workorder0_.subscriber_nbr as subscri11_28_,
    workorder0_.wo_type as wo_type12_28_ 
from
    workorders workorder0_ 
where
    workorder0_.sched_date=?

我使用以下查询来验证列的数据类型。

SELECT ST.tabname, SC.colname, SC.coltype
FROM systables ST
JOIN syscolumns SC ON SC.tabid = ST.tabid
JOIN sys
WHERE ST.tabname="wo_owners";

它返回一个结果:

tabname     wo_owner_nbr    coltype
wo_owners   wo_owner_nbr    262
wo_owners   owner_id    271

列出了 Informix coltypes here

262 - 256 = 6 = SERIAL
271 - 256 = 15 = NCHAR

注意* 在 DB-Access 中,偏移值 256 总是添加到这些 coltype 代码中,因为 DB-Access 将 SERIAL、SERIAL8 和 BIGSERIAL 列设置为 NOT NULL。 source

“Informix® 支持 SERIAL、SERIAL8 和 BIGSERIAL 数据类型来生成自动整数序列。SERIAL 基于 INTEGER(32 位)”source

【问题讨论】:

  • 这里是你的朋友。请注意堆栈跟踪中的最后一个 Caused By。该字段在您的实体中设置为整数,但它似乎来自数据库作为字符串。这显然不行。
  • 我现在看到您在 #2 点中指出了我的上述发现。这是绝对相关的,意味着在您的数据库中,workorder_nbr 列的类型为 varchar 或 text 或其他字符串类型。检查您的数据库表定义。
  • 工作订单中的 wo_owner_nbr 是一个 int。 wo_owners 中的 wo_owner_nbr 是连续剧。我已使用此信息编辑了原始帖子。

标签: java hibernate spring-data spring-data-jpa querydsl


【解决方案1】:

从我们看到的情况来看,确实看起来有来自数据库的字符串,但您确保该列被定义为整数数据类型。我只是查看了串行语法,它似乎在 postgres 中定义了整数列和一个序列。

因此,我没有看到直接的答案,但想建议一些事情来追踪问题。

  1. use this to see the actual datatype 列在数据库中。

  2. 加载id涉及的每个JPA实体,验证问题是否真的是WorkOrderOwner问题

  3. 删除与问题无关的所有属性

  4. number属性改为String,看看问题是否消失

  5. 确认您没有 getter 和 setter,或者它们具有正确的数据类型

  6. 让我们知道您的发现

【讨论】:

  • 2.我可以使用集成测试按 ID 加载每个 JPA 实体。
【解决方案2】:

@Transactional 注释需要在公共方法上。您可以将注释移至调用您在代码 sn-p 中的受保护方法的公共方法,也可以将受保护方法更改为公共方法。

【讨论】:

  • 谢谢,我应该明白这一点。我已经修复了,它实际上解决了我遇到的另一个问题,但主要问题仍然存在。
【解决方案3】:

确切的问题与 Hibernate 或 Spring Data 无关,它与我在 UI 上投射从 Select 元素派生的 ArrayLists 的方式有关。在 WorkOrderPredicates 我这样做了:

List<String> inValues = (List<String>) criteria.getValue();

这使得值成为一个字符串,并且它完全不能被分配给一个整数。

更糟糕的是,我后来把它改成了这样:

  if (criteria.getType().equals(SelectionDataType.INTEGER))
  {
    ArrayList<Integer> values = criteria.getValue();
    predicates.add(entityPath.get(criteria.getField()).in((values)));
  }
  else if (criteria.getType().equals((SelectionDataType.STRING)))
  {
     ArrayList<String> values = criteria.getValue();
    predicates.add(entityPath.get(criteria.getField()).in((evalues)));
  }

这里,因为泛型在运行时被删除,所以 ArrayList 仍然是一个 ArrayList ,即使它看起来像是我正在投射它。

我的解决方案是像这样构建新数组:

  if (criteria.getType().equals(SelectionDataType.INTEGER))
  {
    predicates.add(entityPath.get(criteria.getField()).in((extractArrayListInteger(criteria))));
  }
  else if (criteria.getType().equals((SelectionDataType.STRING)))
  {
    predicates.add(entityPath.get(criteria.getField()).in((extractArrayListString(criteria))));
  }

创建新数组列表的方法

  @SuppressWarnings("unchecked")
  private static ArrayList<Integer> extractArrayListInteger(SelectedCriteria criteria)
  {
    ArrayList<Integer> values = new ArrayList<>();
    for (Object v : (ArrayList<Object>) criteria.getValue())
    {
      values.add(Integer.valueOf(v.toString()));
    }
    return values;
  }

  @SuppressWarnings("unchecked")
  private static ArrayList<String> extractArrayListString(SelectedCriteria criteria)
  {
    ArrayList<String> values = new ArrayList<>();
    for (String v : (ArrayList<String>) criteria.getValue())
    {
      values.add(v);
    }
    return values;
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多