I have a table with a JSONB column in Postgres. Let's call it pet.
| id | name | details |
|---|---|---|
| 1 | Cat | {"furColour": "brown"} |
| 2 | Dog | {"coatColour": "black", "bark": "loud"} |
| 3 | Parrot | {"beakColour": "red", "featherColour": "green"} |
I'm trying to come up with a Spring Data JPA specification that can filter the records of this table based on the attributes of the details column. However, I need to be able to filter by attributes ending with Colour, not the exact attribute name. I was able to achieve this by using a Postgres query such as this one:
select * from pet
where exists (
select from jsonb_each_text(details) as kv
where kv.key like '%Colour'
and kv.value = 'red');
I'm finding it extremely challenging to come up with a JPA specification for the above where clause. I have tried this:
(root, criteria, builder) -> {
Expression<Tuple> function =
builder.function("jsonb_each_text", Tuple.class, root.get("details"));
// Not sure how to set alias or if it's even
// necessary as in my SQL
var kv = function.alias("kv");
var subQuery = builder.createQuery().subquery(Tuple.class).select(function);
// I'm unable to work out how to determine this root.
// I cannot use Tuple as a root as it's obviously not
// an entity and thus I get an appropriate error.
var functionRoot = criteria.from(Tuple.class);
return builder.exists(subQuery.where(builder.like(functionRoot.get("key"), "%Colour"), builder.equal(functionRoot.get("value"), "red")));
};
My Pet entity class looks like this:
import com.vladmihalcea.hibernate.type.json.JsonType;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import javax.persistence.Column;
import javax.persistence.Entity;
// Other imports skipped for brevity
@Getter
@Setter
@Entity
@TypeDef(name = "json", typeClass = JsonType.class)
public class Pet {
@Id
@GeneratedValue
private UUID id;
private String name;
@Type(type = "json")
@Column(columnDefinition = "jsonb")
private Map<String, Object> details = Collections.emptyMap();
}
What am I doing wrong here?
Any tips, pointers, guidance here would be greatly appreciated.
Requested attribute was not a maperror and I cannot use the first option because it uses internal Hibernate API (even if I move to JPA API, it doesn't compile). I think for now I'll write a custom DB function and revisit this later when we migrate to Spring Boot 3 and JPA 3 (Hibernate 6).