I have a custom UserType (call it FooType) that maps 3 values from a POJO class called Foo. Class Foo has several values in it but only 3 are read/written to the database via the FooType UserType:
Everything is working, including JPA repository queries that return List<Foo> (i.e. just Foo-mapped values from the three mapped columns in my parent table).
What does not work is the following update query in FooRepository:
@Modifying
@Query(value = "UPDATE FooEntity SET foo = :foo WHERE id = :id")
int updateFoo(@Param("foo") Foo foo, @Param("id") Long id);
I get this error:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: AND near line 1
How do I make this work? Note that I’m not using an embeddable because there’s business logic magic in FooType that transforms the data in Foo before it gets written to the database (ditto on reads).
That’s what I originally tried, but I get this error during initialization:
java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: prop1
Hibernate behaves as if it can’t recognize any property of Foo.
The Foo POJO has several fields which aren’t directly persisted, for example:
public class Foo {
private A a;
private B b;
private C c;
private D d;
}
FooType does transformation on these values to determine the final values of col1, col2, and col3.
I expect Hibernate query language will recognize any field in Foo, no matter how or if it’s mapped to an actual column by FooType, but UPDATE FooEntity SET foo.a = null, for any field in Foo, results in the error could not resolve property: a.
You are aware that you can only access persistent attributes, right? That’s just how this works. Anything else would be a request for a new feature. I’m not sure what you expect Hibernate to do or generate if you use an attribute in an UPDATE query that is not a persistent attribute.
It’s a custom type, so Hibernate cannot know what fields are persisted. I expect it to call the setters on Foo, and then call FooType.nullSafeSet.
This works for all other repository methods and is how Hibernate types work in general; the underlying Hibernate type determines what columns are persisted, not the POJO in the entity.
When implementing org.hibernate.type.CompositeType you can provide property names. In general, I would recommend you use @Embeddable for the type if you are in control of the source code. If not, then use the CompositeType approach. This way you can access the individual properties.
So I implemented CompositeUserType and I can do queries/updates using @Query; for example:
@Table("Foo")
public class FooEntity {
@Id
private long id;
@Columns({@Column("col1"),@Column("col2"),@Column("col3")})
private Foo foo;
}
public class Foo {
private String a,b,c,d;
}
public class FooType implements CompositeUserType {
@Override
public String[] getPropertyNames() {
return new String[] {"property1","property2","property3"};
}
}
Note that the fields in Foo (a,b,c,d) from above are deliberately not the same as the FooType parameters (or the actual db columns), because FooType does magic transformation from the Foo class fields to to its properties property1/property2/property3, and applies converters to write the values to the columns col1/col2/col3.
This works fine:
public class FooRepository extends JpaRepository<FooEntity, Long> {
@Query("SELECT foo FROM FooEntity")
List<Foo> findAllValues();
@Query("UPDATE FooEntity SET foo.property1 = :prop1Value, foo.property2 = :prop2Value")
int updateFoo(@Param("prop1Value") String prop1Value, @Param("prop2Value") prop2Value));
}