0

In my Java code, I have a JPA entity define as below

public class Entity {

    @Id
    private Long id;

    ... other attributes;

    @Column(name = "ctx")
    @Convert(converter = MapToJsonStringConverter.class)
    private Map<String, String> ctx;
}

@Convert
public class MapToJsonStringConverter implements AttributeConverter<Map<String, String>, String> {

    @Autowired
    private JsonParserService jsonParserService;

    @Override
    public String convertToDatabaseColumn(final Map<String, String> map) {
        return Optional.ofNullable(map)
                .map(jsonParserService::writeValueAsString)
                .orElse(null);
    }

    @Override
    public Map<String, String> convertToEntityAttribute(final String string) {
        return Optional.ofNullable(string)
                .map(jsonParserService::readValueAsMap)
                .orElse(null);
    }
}

A Jpa attribute converter is used to convert the ctx attribute to/ from a JSON string. I need to create a JPA specification that allows me to query the ctx field in the database using a LIKE clause:

ctx LIKE '%test-value%'

Of course, when I try to create such specification the criteria builder cannot match the type of the attribute as it is expecting a map when instead I only supply a string

cb.like(root.get(Entity_.ctx), string))  <--- compile error Cannot resolve method 'like(javax.persistence.criteria.Expression<M>, java.lang.String)'

Is there a way to make this work instead of using a native query? Consider that the final specification also involves other attributes of the same entity.

1 Answers1

1

As mentionned in commentaries

cb.like(root.<String>get("ctx"), clause);

Does not work as root.get(Entity_.ctx) or its equivalent does return a Map<String, String> and explicitly typing the generic (<String>get(...)) won't help as Map is not instanceof String. The solution as mentionned here is to use as provided by Criteria.

cb.like(root.get(Entity_.ctx).as(String.class), string)

It does work thanks to Map overriding Object.toString(). Otherwise you would need to create your own class extending Map and custom the override to toString() to return your custom representation (which is used by default by Json libs).

IQbrod
  • 2,060
  • 1
  • 6
  • 28
  • Just one more on this specific answer, this essentially works fine but at the same time it is casting to a VARCHAR(255) meaning that the column is truncated during the search. Is there a way to cast that to a TEXT ? – user2151096 Feb 25 '21 at 11:34