Jonathan
Jonathan

Reputation: 546

Hibernate search ManyToMany with spatial search

I have an Entity A with Hibernate Search @Spatial data like this:

@Entity
@Spatial(spatialMode = SpatialMode.HASH)
@Indexed
public class A {

    @Id
    @GeneratedValue(generator = "uuid2")
    private UUID id;

    private Double latitude;

    private Double longitude;

    @Spatial(spatialMode = SpatialMode.HASH)
    @SortableField
    public Coordinates getLocation() {
        return new Coordinates() {
            @Override
            public Double getLatitude() {
                return latitude;
            }

            @Override
            public Double getLongitude() {
                return longitude;
            }
        };
    }

    @ManyToMany(fetch = FetchType.EAGER)
    @IndexedEmbedded
    private Set<B> multipleB = new HashSet<>();
}

Entity A has a ManyToMany Relationship with Entity B. Entity A has Spatial Information that are important to Entity B. Now I want to perform a Spatial Query on Entity B, that uses the location Information of Entity A.

@Entity
@Indexed
public class B {
    @Id
    @GeneratedValue(generator = "uuid2")
    private UUID id;

    @ManyToMany
    @ContainedIn
    private Set<A> multipleA = new HashSet<>();
}

I tried to do a Query for Entity B like this:

QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(B.class).get();

    Query luceneQuery = qb.
            spatial()
            .onField("multipleA.location")
            .within(kmRadius, Unit.KM)
            .ofLatitude(centerLatitude)
            .andLongitude(centerLongitude)
            .createQuery();

However the Problem is, that when I query like this I get the following exception:

The field 'B#multipleA.location' used for the spatial query is not configured as spatial field. Check the proper use of @Spatial respectively SpatialFieldBridge.

Is there a way to do spatial queries on Entities of Type B that use the spatial data from the connected Entities A with @ManyToMany?

For my Queries it would also be acceptable if the same Entity B is returned multiple times for each connected Entity A (like it would with a join).

Example (a_1 is the closest Entity A)

Database: b_1 -> (a_1, a_2), b_2 -> (a_1), b_3 -> (a_2, a_3)

Query Result:

b_1 (a_1 is the closest)
b_2 (a_1)
b_1 (a_2 is the second closest)
b_3 (a_2)
b_3 (a_3)

Upvotes: 0

Views: 416

Answers (1)

yrodiere
yrodiere

Reputation: 9977

Multi-valued locations are not supported in Hibernate Search; even if you manage to index it, the query just won't work properly (it will mix the latitude and longitude from different points).

The only way I can see this working would be if you modeled your many-to-many association as an entity, indexed that entity, and queried it instead of B.

Also, note the error you're getting is related to another issue: the field you're targeting does not exist. If you wanted it to exist, you would need to add an @IndexedEmbedded on B#multipleA.

Here is something that should work better:

@Entity
@Spatial(spatialMode = SpatialMode.HASH)
@Indexed
public class A {

    @Id
    @GeneratedValue(generator = "uuid2")
    private UUID id;

    private Double latitude;

    private Double longitude;

    @Latitude
    public Double getLatitude() {
        return latitude;
    }

    @Longitude
    public Double getLongitude() {
        return longitude;
    }

    @OneToMany(mappedBy = "a")
    @ContainedIn
    private Set<AToB> multipleB = new HashSet<>();
}

@Entity
@Indexed
public class AToB {
    @Id
    @GeneratedValue(generator = "uuid2")
    private UUID id;

    @ManyToOne
    @IndexedEmbedded(includePaths = "location") // includePaths will be necessary to avoid infinite recursion if you really want an @IndexedEmbedded from A to B
    private A a;

    @ManyToOne
    @IndexedEmbedded
    private B b;
}

@Entity
public class B {
    @Id
    @GeneratedValue(generator = "uuid2")
    private UUID id;

    @OneToMany(mappedBy = "b")
    @ContainedIn
    private Set<AToB> multipleA = new HashSet<>();
}

Then query like this:

QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(AToB.class).get();

    Query luceneQuery = qb.
            spatial()
            .onField("a.location")
            .within(kmRadius, Unit.KM)
            .ofLatitude(centerLatitude)
            .andLongitude(centerLongitude)
            .createQuery();

Upvotes: 2

Related Questions