Search code examples
hibernate-search

Hibernate Search query searches all tables instead of only the specified class' table


I have an abstract class Product that are subclassed by ProductA, ProductB and ProductC.
And the classes ProductA, ProductB and ProductC are mapped to the database tables PRODUCTS_A, PRODUCTS_B and PRODUCTS_C respectively.

Now I want to perform a full-text search on ProductA entities by Hibernate Search.
I wrote some code to successfully get expected ProductA entities from database, but I found in the log (as follows) that the executed Hibernate Search query actually searched all the tables PRODUCTS_A, PRODUCTS_B and PRODUCTS_C instead of only the table PRODUCTS_A I expected.

I want to get only ProductA entities, why are ProductB and PRODUCTS_C tables also searched? Is there a way to fix this?

Log

You can see from the following working log outputted by Hibernate that besides the PRODUCTS_A table, the PRODUCTS_B and PRODUCTS_C tables are also searched.


Hibernate: select this_.ID as ID1_2_0_, this_.NAME as NAME2_2_0_, this_.FEATURE as FEATURE3_2_0_, this_.CREATED_DATE as CREATED_4_2_0_, this_.MODIFIED_DATE as MODIFIED5_2_0_, this_.FEATURE_A1 as FEATURE_1_3_0_, this_.FEATURE_A2 as FEATURE_2_3_0_, this_.FEATURE_B1 as FEATURE_1_4_0_, this_.FEATURE_B2 as FEATURE_2_4_0_, this_.FEATURE_C1 as FEATURE_1_5_0_, this_.FEATURE_C2 as FEATURE_2_5_0_, this_.clazz_ as clazz_0_ from ( select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, FEATURE_A1, FEATURE_A2, null::varchar as FEATURE_B1, null::varchar as FEATURE_B2, null::varchar as FEATURE_C1, null::varchar as FEATURE_C2, 1 as clazz_ from PRODUCTS_A union all select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, null::varchar as FEATURE_A1, null::varchar as FEATURE_A2, FEATURE_B1, FEATURE_B2, null::varchar as FEATURE_C1, null::varchar as FEATURE_C2, 2 as clazz_ from PRODUCTS_B union all select ID, NAME, FEATURE, CREATED_DATE, MODIFIED_DATE, null::varchar as FEATURE_A1, null::varchar as FEATURE_A2, null::varchar as FEATURE_B1, null::varchar as FEATURE_B2, FEATURE_C1, FEATURE_C2, 3 as clazz_ from PRODUCTS_C ) this_ where (this_.ID in (?))


Code

Entity Classes

Here are the entity classes Product, ProductA, ProductB and ProductC.


public abstract class Product {
    @Id
    protected Long id;

    @Field
    protected String name;

    @Field
    protected String feature;

    protected Date createdDate;

    protected Date modifiedDate;

    // Getters and setters...
}

@Entity
@Indexed
public class ProductA extends Product {
    @Field
    private String featureA1;

    @Field
    private String featureA2;

    public ProductA() {
    }

    // Getters and setters...
}

The ProductB and ProductC classes are similar as the ProductA class.


Hibernate Mapping File

Product.hbm.xml
The union-subclass element is used to reflect the subclass relationship between the Product class and the ProductA, ProductB and ProductC classes.


<hibernate-mapping package="com.raychen518.study.hibernate">
    <class name="Product" abstract="true">
        <id name="id" column="ID">
            <generator class="increment" />
        </id>
        <property name="name" column="NAME" />
        <property name="feature" column="FEATURE" />
        <property name="createdDate" type="timestamp" column="CREATED_DATE" />
        <property name="modifiedDate" type="timestamp" column="MODIFIED_DATE" />
        <union-subclass name="ProductA" table="PRODUCTS_A">
            <property name="featureA1" column="FEATURE_A1" />
            <property name="featureA2" column="FEATURE_A2" />
        </union-subclass>
        <union-subclass name="ProductB" table="PRODUCTS_B">
            <property name="featureB1" column="FEATURE_B1" />
            <property name="featureB2" column="FEATURE_B2" />
        </union-subclass>
        <union-subclass name="ProductC" table="PRODUCTS_C">
            <property name="featureC1" column="FEATURE_C1" />
            <property name="featureC2" column="FEATURE_C2" />
        </union-subclass>
    </class>
</hibernate-mapping>

Hibernate Configuration File

hibernate.cfg.xml


<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.postgresql.Driver</property>
        <property name="connection.url">jdbc:postgresql://localhost:5432/test</property>
        <property name="connection.username">postgres</property>
        <property name="connection.password">admin</property>
        <property name="connection.pool_size">1</property>
        <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>

        <property name="current_session_context_class">thread</property>
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">validate</property>

        <!-- Setting for Hibernate Search -->
        <property name="hibernate.search.lucene_version">LUCENE_CURRENT</property>
        <property name="hibernate.search.default.directory_provider">filesystem</property>
        <property name="hibernate.search.default.indexBase">hibernate.search.test/lucene/indexes</property>

        <mapping resource="Product.hbm.xml" />
    </session-factory>
</hibernate-configuration>

Application Launcher Class

The ProductManager class contains the main method, thus serves as the application launcher. It starts the Hibernate Search indexing process, clears the PRODUCTS_A, PRODUCTS_B and PRODUCTS_C tables and inserts some sample product data into them, and finally performs a full-text search using the Hibernate Search.
What confuses me is that I have specified the target entity as ProductA.class in the following statement Query query = fullTextSession.createFullTextQuery(luceneQuery, ProductA.class); in the method searchProducts(). Why does Hibernate Search also search ProductB and ProductC entities?


public class ProductManager {

    public static void main(String[] args) throws InterruptedException {
        ProductManager productManager = new ProductManager();
        productManager.indexAllProducts();
        productManager.deleteAllProducts();
        productManager.generateSomeProducts();
        productManager.searchProducts();
    }

    private void indexAllProducts() throws InterruptedException {
        FullTextSession fullTextSession = Search.getFullTextSession(HibernateUtil.getSessionFactory().getCurrentSession());
        fullTextSession.createIndexer().startAndWait();
    }

    public void deleteAllProducts() {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        @SuppressWarnings("unchecked")
        List<Product> results = session.createQuery("from Product").list();

        for (Product result : results) {
            session.delete(result);
        }

        session.getTransaction().commit();
    }

    public void generateSomeProducts() {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        session.save(new ProductA("feature001", "featureA1001", "featureA2001", new Date()));
        session.save(new ProductA("feature002", "featureA1002", "featureA2002", new Date()));
        session.save(new ProductA("feature003", "featureA1003", "featureA2003", new Date()));

        session.save(new ProductB("feature001", "featureB1001", "featureB2001", new Date()));
        session.save(new ProductB("feature002", "featureB1002", "featureB2002", new Date()));
        session.save(new ProductB("feature003", "featureB1003", "featureB2003", new Date()));

        session.save(new ProductC("feature001", "featureC1001", "featureC2001", new Date()));
        session.save(new ProductC("feature002", "featureC1002", "featureC2002", new Date()));
        session.save(new ProductC("feature003", "featureC1003", "featureC2003", new Date()));

        session.getTransaction().commit();
    }

    private void searchProducts() {
        FullTextSession fullTextSession = Search.getFullTextSession(HibernateUtil.getSessionFactory().getCurrentSession());
        fullTextSession.beginTransaction();

        QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(ProductA.class).get();
        org.apache.lucene.search.Query luceneQuery = queryBuilder.keyword().onFields("feature").matching("feature002").createQuery();

        // Set the 2nd method parameter using "Product.class" to get products of the types ProductA, ProductB and ProductC.
        // Set the 2nd method parameter using "ProductA.class" to get products of the types ProductA.
        Query query = fullTextSession.createFullTextQuery(luceneQuery, ProductA.class);

        @SuppressWarnings("unchecked")
        List<Product> queryResults = query.list();
        for (Product queryResult : queryResults) {
            System.out.println("queryResult: " + queryResult);
        }

        fullTextSession.getTransaction().commit();
    }

}


Solution

  • I fixed it recently as https://hibernate.atlassian.net/browse/HSEARCH-2301 following another Stackoverflow question.

    It hasn't been released yet but the patch is rather small and localized in only one file: https://github.com/hibernate/hibernate-search/pull/1122/files so you should be able to apply it locally on the 5.5 branch.

    Use https://patch-diff.githubusercontent.com/raw/hibernate/hibernate-search/pull/1122.diff to get the raw diff file.

    UPDATE we fixed it in 5.5.4.Final: http://in.relation.to/2016/06/29/Polishing-Polishing-And-More-Polishing-Hibernate-Search-5-5-4-Final/