【问题标题】:Native named query fails with exception "column is of type date but expression is of type bytea"when NULL LocalDate is given as input当输入 NULL LocalDate 时,本机命名查询失败并出现异常“列的类型为 date 但表达式的类型为 bytea”
【发布时间】:2019-02-06 01:44:22
【问题描述】:

查询:

    INSERT INTO PERSON 
        (email, mobile, party_id, affiliate_id, eligibility, member_start_date, created_by, created_dt, first_name, last_name, google_connected) 
        values 
        ('xxx@yyy.org', NULL, 123, '123', '1', NULL, NULL, '2018-8-30 21:45:56.859000 -6:0:0', 'xxx', 'yyy', '0')
        ON CONFLICT (email) 
        DO UPDATE SET create_dt = '2018-8-30 21:45:56.859000 -6:0:0' where email = ?

当 LocalDate 值不为空时,它工作正常。仅当 LocalDate 值为 null 时才面临此问题。

即使在 PostgreSQL 转换之后,它也是一样的。

异常堆栈跟踪:

2018-08-30 21:10:48,372 -- [ERROR]-- 出现意外问题 与您的请求 org.postgresql.util.PSQLException: ERROR: column "member_start_date" 是 date 类型,但表达式是 bytea
提示:您将需要重写或强制转换表达式。职位:185 在 org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2182) 在 org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1911) 在 org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:173) 在 org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:645) 在 org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:495) 在 org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:380) 在 sun.reflect.GeneratedMethodAccessor98.invoke(Unknown Source) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) 在 com.sun.proxy.$Proxy185.executeQuery(Unknown Source) 在 org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:70) ...省略了149个常用框架

实体:

@Entity(name = "person")
@EqualsAndHashCode(callSuper = false)
public class PersonEntity extends Audit {

@Id
@GeneratedValue
@Column(name = "person_id", columnDefinition = "uuid", updatable = false)
private UUID id;

@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
private String lastName;

@Column(name = "email")
@NotNull
private String email;

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

@Column(name = "party_id")
private Long partyId;

@Column(name = "affiliate_id")
private String affiliateId;

@Column(name = "eligibility")
@NotNull
private Boolean eligibility;

@Column(name = "member_start_date")
private LocalDate memberStartDate;

@Column(name = "google_connected")
private Boolean googleAccountConnected;
}

PostgreSQL 表定义;它缺少不重要的google_connected 列:

CREATE TABLE person
( 
   person_id             UUID NOT NULL,
   email                 VARCHAR(128) NOT NULL,
   mobile                VARCHAR(20), 
   party_id              INTEGER,
   affiliate_id          VARCHAR(20),
   eligibility              BOOLEAN NOT NULL,
   member_start_date     DATE, 
   created_by            VARCHAR(128) NOT NULL,
   created_dt            TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
   updated_by            VARCHAR(128) DEFAULT NULL,
   updated_dt            TIMESTAMP NULL,
   CONSTRAINT person_pk PRIMARY KEY ( person_id )
 );

【问题讨论】:

  • 可以分享一下你的实体类吗?
  • 向我们展示 Postgres 中的表定义可能会有所帮助。

标签: java postgresql hibernate spring-data


【解决方案1】:

因为查询是本机的,Hibernate 不知道预期的数据类型,所以当您传递 null 时,它默认为通用 Serializable 类型处理程序。更改此行为会破坏与其他数据库的兼容性。

然而,Postgres 会立即解析查询并确定可接受的类型,并且它总是在检查 null 之前检查类型。他们是唯一可以解决此问题的人,但拒绝这样做并说它按预期工作。

您唯一的解决方案是:

  • 使用 JPQL
  • 使用托管实体
  • 在需要的查询字符串中使用硬编码的空值

幸运的是,对于第三个选项,借助 Hibernate,您可以在本机查询中使用命名参数,因此您不必为某项可用和何时不可用进行位置计算。

编辑:此后我发现的第四个解决方案。

您有您的疑问:

Query q = em.createNativeQuery("UPDATE...");

在某处有一些静态的最终 LocalDate 对象:

public static final LocalDate EPOCH_DATE = LocalDate.of(1970, 1, 1);

然后像这样调用查询:

q.setParameter("start_date", EPOCH_DATE);
q.setParameter("start_date", nullableDateParam);

第一次为参数调用 setParameter 时,Hibernate 使用类来解析类型。第二次调用时,类型已经解析,所以 null 将起作用。

【讨论】:

  • 知道为什么这个问题只发生在日期列而不发生在其他列吗?
  • 取决于 Postgres 中的隐式类型转换函数。您可以使用\dC 在 psql 控制台中列出它们,尽管我对它的理解并不比这更深。
  • 你可以在设置参数时明确告诉 hibernate 类型(类型在 org.hibernate.type 中),而不是你厚颜无耻的双参数集。例如q.setParameter("start_date", nullableDateParam, LocalDateType.INSTANCE)
  • @RobertDiPaolo 不是 JPA 接口的一部分。您必须进行类型转换。
  • 对,我不确定纯 JPA 和 Hibernate 接口之间的区别是什么。但是使用 Hibernate (5.2),这对我有用(使用 InstantType.INSTANT)。您的解决方案也有效,但是这样就无需设置两次参数。
【解决方案2】:

这是一个老问题,但有一个更有用的方法:

您的查询... .setParameter("transaction_id", null, LongType.INSTANCE)

它有效。 发现自https://forum.hibernate.org/viewtopic.php?p=2493645

【讨论】:

    【解决方案3】:

    转到较新版本的 hibernate 5.1.17 及更高版本 + postgres 似乎已经表现出这种行为。查看代码,当它绑定一个没有值的类型时,旧的休眠代码试图通过类型解析器解析该类型。较新版本的 hibernate 实现声明它不会猜测。

      public Type resolveParameterBindType(Object bindValue) {
            if ( bindValue == null ) {
                // we can't guess
                return null;
            }
    

    我们最终只是先根据类型设置了一个默认值,然后才是真正的空值。

    【讨论】:

    • 嗯,这是一个老问题,但却是一个新答案。错误可能是由于声明的类型与 created_dt 的插入值之间的差异造成的。它被定义为timestamp without timezone,但插入的值是带有时区的时间戳。我一直觉得价值观和定义相匹配会更好; 不要相信隐含的翻译
    猜你喜欢
    • 2023-03-15
    • 2015-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-19
    • 1970-01-01
    • 2023-02-09
    • 2018-03-31
    • 1970-01-01
    相关资源
    最近更新 更多