Custom type update query gives "unexpected AST node" error

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:

@Columns({@Column("col1"),@Column("col2"),@Column("col3")})
@Type(type="FooType")
private Foo foo;

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).

You can return property names in the custom type which you can use in the UPDATE query then like this:

    @Query(value = "UPDATE FooEntity SET foo.prop1 = :foo_prop1, foo.prop2 = :foo_prop2 WHERE id = :id")

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.

How is this different?

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.

I think implementing CompositeType is probably what I’m looking for, I’ll give that a shot. Thank you!

1 Like

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));
}

However when I try this CriteriaUpdate:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaUpdate<FooEntity> criteria = builder.createCriteriaUpdate(FooEntity.class);
Root<FooEntity> root = criteria.from(FooEntity.class);
criteria.set(root.get("foo.property1"), "my property 1 value");
entityManager.createQuery(criteria).executeUpdate();

I get this error:

java.lang.IllegalArgumentException: Unable to locate Attribute  with the the given name [foo.property1] on this ManagedType [FooEntity]

What’s causing the error?

The problem is that composite types are not exposed as embeddables to JPA. This was only recently fixed as part of HHH-14198 and is released in 5.4.22

1 Like

Also, you have to use root.get("foo").get("property1")

1 Like

That worked like a charm on 5.4.22.Final. Thank you!