Updating "dependent" entity's index

Hi!

I have 2 entities - Manufacturer and Product.
Manufacturer has property “status” which can be Pending, Enabled, Disabled…
And of course, Product entity has a property Manufacturer.
In order to have fast search on Product entity, I use Hibernate Search.
In Product entity I have a bridge to Manufacturer which puts “STATUS_ENABLED” or “STATUS_DISABLED” to the Product index depending on the status of its Manufacturer.
Using that I can filter out products from disabled manufacturers without SQL and joins.
Later when I update Manufacturer by changing its state (e.g. from Enabled to Disabled), product index is not updated (of course).

Is there any way to bind properties between entities or make a dependency somehow or anything similar in order to initiate automatic updates on dependent entity index?

I am aware it can be done manually by triggering index rebuild, but I would like to avoid this. Also I could get all the products from updated manufacturer and update them one by one, but still it takes more effort comparing to automatic update that might be configured.

Thanks!

(Assuming you’re using Search 5.x)

If you have an association from manufacter to product, you can put @ContainedIn on that property:

@Entity
public class Manufacturer {

  // ...

  @OneToMany(mappedBy = "manufacturer")
  @ContainedIn
  private List<Product> products = new ArrayList();

  // ...

}

See https://docs.jboss.org/hibernate/search/5.11/reference/en-US/html_single/#search-mapping-associated

Of course this will force Hibernate Search to load this association every time you change a manufacturer in a way that could impact the product, so this will not work if there are two million products per manufacturer (you’d get an out of memory error). In that case, your only solution will be manual reindexing, taking care to clear the session from time to time: https://docs.jboss.org/hibernate/search/5.11/reference/en-US/html_single/#search-batchindex-flushtoindexes

1 Like

Thank you very much!

This is the summary from my code. I don’t have list of products inside manufacturer as I want it to be fast. Thus I have ManufacturerBridge which adds Manufacturer ID to each Article in Lucene index. When I want a list of all Articles from specific Manufacturer, I just query Lucene for e.g. “Manufacturer_250”. I can also filter only active Articles from Manufacturers as I create this information in bridge - query “ManufacturerStatus_ACTIVE”.
Introducing a list of Articles to each Manufacturer maybe would be acceptable, but I also have a Provider which represent sale point of Article, and one single Article can be assigned to many Providers and also each Provider can have much more Articles than a Manufacturer.

I’ll try to update Articles at the time of Manufacturer change (and Provider change) because this happens pretty rare and it does not have to be at the speed of light and try to avoid introducing additional lists especially because of potential circular references.

Thanks again for your answer!

BR,
Hrvoje

@Entity @Indexed
public class Article {
	private String name;

	@ManyToOne(fetch = FetchType.EAGER)
	@Field(
		index=Index.YES, analyze=Analyze.YES, store=Store.NO,
		bridge = @FieldBridge(impl = ManufacturerBridge.class)
	)
	private Manufacturer manufacturer;
}

@Entity @Indexed
public class Manufacturer {
	public enum Status { ACTIVE, HIDDEN, DISABLED, DELETED }
	private String name;
	private Status status;
}

public class ManufacturerBridge implements StringBridge {
	if (object instanceof Manufacturer) {
		final Manufacturer manufacturer = (Manufacturer) object;
		final Set<String> keywords = new HashSet<>();
		keywords.add("Manufacturer_" + manufacturer.getId().toString());
		keywords.add(manufacturer.getName());
		keywords.add("ManufacturerStatus_" + manufacturer.getStatus().name());
		return String.join(" ", keywords);
	}
	return "";
}

Just to share my solution with others if anyone ever runs into the same problem.
Inside service layer I modified Manufacturer.save() by adding a loop that goes through the articles of given Manufacturer and calls Article.update() on each. Which activates ManufacturerBridge and updates Lucene index on Articles.
Maybe it’s not the fastest update, but keeps reads from Manufacturer faster because there is no additional List of articles inside Manufacturer.

@Transactional
public Manufacturer save(final Manufacturer m) {
	int pageNumber = 0;
	final SearchReq req =
		new SearchReq.Builder()
			.manufacturerId(m.getId())
			.pageSize(100) 
			.build();
	while(true) {
		req.setPage(pageNumber++);
		final Page<Article> page = this.searchService.luceneSearch(req, Article.class);
		for (final Article a : page.getContent()) {
			this.articleService.update(a);
		}
		if(!page.hasNext()) {
			break;
		}
	}
	return this.manufacturerRepository.save(m);
}