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();
}
}
我有一个带有 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();
}
}