Reputation: 182
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?
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 (?))
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.
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.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>
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();
}
}
Upvotes: 1
Views: 852
Reputation: 10529
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/
Upvotes: 3