3

First Issue: I have an existing code written for hibernate spatial 5 to find records within given radius, which works fine. I am in the process of migrating the code hibernate-spatial 6.1.7.Final. But getting below error:

java.lang.IllegalArgumentException: Passed `invariantType` for function return cannot be null at org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.invariant(StandardFunctionReturnTypeResolvers.java:45) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final] at org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.function(SqmCriteriaNodeBuilder.java:1495) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final] at org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder.function(SqmCriteriaNodeBuilder.java:153) ~[hibernate-core-6.1.7.Final.jar:6.1.7.Final] at com.adani.amm.specification.AssetLocationSpecification.toPredicate(AssetLocationSpecification.java:35) ~[classes/:na] 

As per my debugging so far, it seems that POSTGIS geography function is not supported in the version. Below is a snippet of my code.

public class AssetLocationSpecification implements Specification<Location> { /** * */ private static final long serialVersionUID = 1L; private final Double radius; private final Double latitude; private final Double longitude; public AssetLocationSpecification(Double radius, Double latitude, Double longitude) { super(); this.radius = radius; this.latitude = latitude; this.longitude = longitude; } @Override public Predicate toPredicate(Root<Location> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { Expression<Geometry> geography = criteriaBuilder.function("geography", Geometry.class, root.get("geometry")); Expression<Point> point = criteriaBuilder.function("ST_Point", Point.class, criteriaBuilder.literal(longitude), criteriaBuilder.literal(latitude)); Expression<Point> centerPoint = criteriaBuilder.function("ST_SetSRID", Point.class, point, criteriaBuilder.literal(4326)); Expression<Boolean> expression = criteriaBuilder.function(SpatialFunction.dwithin.toString(), boolean.class, geography, centerPoint, criteriaBuilder.literal(radius)); return criteriaBuilder.equal(expression, true); } } 

Second Issue: org.hibernate.spatial.SpatialFunction.dwithin is deprecated and replacement enum (org.hibernate.spatial.CommonSpatialFunction) does not contain that function.

1
  • Did you find a solution to this issue? Commented Aug 13, 2023 at 23:07

2 Answers 2

2

I got the same error in a project of mine while performing exactly the same migration. I debugged the Hibernate Spatial code which produced the error, and as far as I understand the way the Hibernate types are registered changed in this version of Hibernate Spatial, and the Geometry type you use as return type in the first statement can't be found in the list of registered basic types. I can't be more specific because I also did not understand the mechanism completely.

After saying that, this error looked to me too weird and made me considering if the mistake was not on my side. I thought maybe I was using the Spatial APi in an "uncommon" way, and I tried to refactor my code trying to be more generic, without using PostGIS functions directly.

My original code was this:

public static Specification<Station> withinBBox( final double lon1,final double lat1, final double lon2, final double lat2) { return new Specification<Station>() { public Predicate toPredicate(Root<Station> root, CriteriaQuery<?> query, final CriteriaBuilder builder) { Expression<Geometry> bBoxExpression = builder.function( "ST_MakeEnvelope", Geometry.class, builder.literal(lon1), builder.literal(lat1), builder.literal(lon2), builder.literal(lat2), builder.literal(4326)); return builder.isTrue( builder.function( "ST_Within", Boolean.class, root.<Point>get("geoLocation"), bBoxExpression) ); } }; } 

And I refactored to this:

public static Specification<Station> withinBBox( final double lon1,final double lat1, final double lon2, final double lat2) { return new Specification<Station>() { public Predicate toPredicate(Root<Station> root, CriteriaQuery<?> query, final CriteriaBuilder builder) { if(builder instanceof SqmCriteriaNodeBuilder) { JTSSpatialCriteriaBuilder jtsBuilder = ((SqmCriteriaNodeBuilder) builder).unwrap(JTSSpatialCriteriaBuilder.class); Expression<? extends Geometry> bBoxExpression = jtsBuilder.literal(GEOMETRY_FACTORY.createPolygon(new Coordinate[] { new Coordinate(lon1, lat1), new Coordinate(lon1, lat2), new Coordinate(lon2, lat2), new Coordinate(lon2, lat1), new Coordinate(lon1, lat1) })); Expression<? extends Geometry> stationLocation = root.<Point>get("geoLocation"); return jtsBuilder.isTrue(jtsBuilder.within(stationLocation, bBoxExpression)); } return builder.isTrue(builder.literal(false)); } }; } 

You can see I do not use PostGIS anymore to create Geometry objects, but a GeometryFactory which I created this way: new GeometryFactory(new PrecisionModel(), 4326);

This approach is database agnostic.

Applying the same approach to your case, you could try to write something like this:

JTSSpatialCriteriaBuilder jtsBuilder = ((SqmCriteriaNodeBuilder) builder).unwrap(JTSSpatialCriteriaBuilder.class); GeometryFactory gf = new GeometryFactory(new PrecisionModel(), 4326); Expression<? extends Geometry> geography = jtsBuilder.literal(root.get("geometry")); Expression<? extends Geometry> centerPoint = jtsBuilder.literal(gf.createPoint(new Coordinate(longitude, latitude))); //This is not needed, the geometryfactory already works with SRID 4326 //Expression<Point> centerPoint = criteriaBuilder.function("ST_SetSRID", Point.class, point, criteriaBuilder.literal(4326)); Expression<Boolean> isWithin = jtsBuilder.distanceWithin(geography, centerPoint, radius); 
Sign up to request clarification or add additional context in comments.

1 Comment

Your examples saved me a huge amount of tedious work, thank you
0

As @Enrico mentioned, the criteriaBuilder.function() method only works with 'basic type' return types, for example ByteArray, String, Boolean etc. It will throw the above exception when you want it to return a complex type like Geometry.

There are ways to work around this, one solution is what @Enrico proposes: not using the Postgis function.

If you do want to use the Postgis functions, you will need to return a basic type. With this basic type you can then do two thing:

  1. use criteriaBuilder.construct() to use the constructor of your complex type to let Hibernate create the instance. You will need a constructor that uses the basic return type as a parameter though;
  2. return the basic type from Hibernate and write code to map this into the complex type programatically.

I used ST_AsText(ST_AsMvtGeom()) to retrieve an MVT geometry as a String from in Kotlin and mapped this to a Geometry instance using the JTS WKTReader.

PS When calling a nested function, it doesn't matter what you put as a return type since Hibernate will never need to map the result. You can call ST_AsMvtGeom() and let Hibernate think the return type is a Boolean and still have functioning queries as long as you wrap the result with ST_AsText() returning a String.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.