HibernateException:无法使用 spring-data-jpa 本机查询 postgresql 确定 class 异常的类型

HibernateException: Could not determine a type for class exception with spring-data-jpa native query for postgresql

我有一个带有 bean 和存储库的 spring 引导应用程序。我使用 postgresql 作为数据库。 数据库有一个叫testjob的table,有两列:id(UUID类型)和parameters(jsonb类型)。 我按照这篇文章来支持jsonb:https://thoughts-on-java.org/persist-postgresqls-jsonb-data-type-hibernate/ 我创建了文章中提到的每个 class,但是当我 运行 我的测试只是调用存储库上的 upsert 方法时,它会抛出一个异常,原因是:

Caused by: org.hibernate.HibernateException: Could not determine a type for class: com.test.Parameters

仅当我 运行 本机查询时才会抛出此异常,但使用 JPQL 一切正常。 可悲的是,我需要在本地使用 运行 它,因为将来我将不得不使用 postgresql ON_CONFLICT 语句。 我会感谢任何建议,谢谢。

我的class是:

TestJobBean.java

@Entity
@Table(name = "testjob")
public class TestJobBean {

    @Id
    private UUID id;

    @NotNull @Column(name = "parameters") @Type(type = "ParametersType")
    private Parameters parameters;
    ...
}
TestJobRepository.java

@Repository
@Transactional
public interface TestJobRepository extends JpaRepository<TestJobBean, UUID>, TestJobRepositoryCustom {
}
TestJobRepositoryCustom.java

public interface TestJobRepositoryCustom {
    int upsert(UUID id, Parameters parameters);
}
TestJobRepositoryCustomImpl.java

@Transactional
public class TestJobRepositoryCustomImpl implements TestJobRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public int upsert(UUID id, Parameters parameters) {
        final Query query = entityManager.createNativeQuery("INSERT INTO testjob (id, parameters) VALUES(:id, :parameters)");
        query.setParameter("id", id);
        query.setParameter("parameters", parameters);
        return query.executeUpdate();
    }

}
package-info.java

@org.hibernate.annotations.TypeDef(name = "ParametersType", typeClass = ParametersType.class)
package com.test.job;
Parameters.java

@Immutable
public class Parameters {

    private final long from;
    private final long to;
    private final @Nonnull String name;

    public Parameters(@JsonProperty("from") long from, @JsonProperty("to") long to,
            @JsonProperty("name") String name) {
        this.from = from;
        this.to = to;
        this.name = name;
    }

    @JsonProperty("from")
    public long getFrom() {
        return from;
    }

    @JsonProperty("to")
    public long getTo() {
        return to;
    }

    @JsonProperty("name")
    public long getName() {
        return name;
    }
}
TestPostgreSQL95Dialect.java

public class TestPostgreSQL95Dialect extends PostgreSQL95Dialect {

    public TestPostgreSQL95Dialect() {
        this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
    }
}
ParametersType.java

public class ParametersType implements UserType {

    private final @Nonnull ObjectMapper mapper = new ObjectMapper();

    @Override
    public int[] sqlTypes() {
        return new int[] {Types.JAVA_OBJECT};
    }

    @Override
    public Class<ParametersType> returnedClass() {
        return ParametersType.class;
    }

    @Override
    public Object nullSafeGet(final ResultSet rs, final String[] names, final SharedSessionContractImplementor session,
            final Object owner) throws HibernateException, SQLException {
        final String cellContent = rs.getString(names[0]);
        if (cellContent == null) {
            return null;
        }
        try {
            return mapper.readValue(cellContent, returnedClass());
        } catch (final IOException ex) {
            throw new RuntimeException("Failed to convert string to an instance!", ex);
        }
    }

    @Override
    public void nullSafeSet(final PreparedStatement ps, final Object value, final int idx,
            final SharedSessionContractImplementor session) throws HibernateException, SQLException {
        if (value == null) {
            ps.setNull(idx, Types.OTHER);
            return;
        }
        try {
            final String valueAsJson = mapper.writeValueAsString(value);
            ps.setObject(idx, valueAsJson, Types.OTHER);
        } catch (final Exception ex) {
            throw new RuntimeException("Failed to convert the instance to string!", ex);
        }
    }

    @Override
    public Object deepCopy(final Object value) throws HibernateException {
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(final Object value) throws HibernateException {
        try {
            final String valueAsJson = mapper.writeValueAsString(value);
            return valueAsJson;
        } catch (final Exception ex) {
            throw new RuntimeException("Failed to convert the instance to string!", ex);
        }
    }

    @Override
    public Object assemble(final Serializable cached, final Object owner) throws HibernateException {
        try {
            return mapper.readValue((String) cached, returnedClass());
        } catch (final IOException ex) {
            throw new RuntimeException("Failed to convert string to an instance!", ex);
        }
    }

    @Override
    public Object replace(final Object original, final Object target, final Object owner) throws HibernateException {
        return original;
    }

    @Override
    public boolean equals(final Object obj1, final Object obj2) throws HibernateException {
        return Objects.equals(obj1, obj2);
    }

    @Override
    public int hashCode(final Object obj) throws HibernateException {
        return obj.hashCode();
    }
}

来自 coladict 的评论:"UserType implementations are not added to the recognized types you can use in native queries" 所以解决方案是将对象转换为字符串并将字符串转换为 jsonb。

TestJobRepositoryCustomImpl.java

@Transactional
public class TestJobRepositoryCustomImpl implements TestJobRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public int upsert(UUID id, Parameters parameters) {
        final String parametersAsString = objectMapper.writer().writeValueAsString(parameters);
        final Query query = entityManager.createNativeQuery("INSERT INTO testjob (id, parameters) VALUES(:id, cast(:parameters as jsonb))");
        query.setParameter("id", id);
        query.setParameter("parameters", parametersAsString);
        return query.executeUpdate();
    }

}