最近,我加入了一个使用Hibernate Search的项目。
我怀疑我们的应用存在故障,由于在2个地方使用FullTextEntityManager
,导致其他后台作业忽略了新索引的数据:
1)在从UI执行目标数据搜索时,我们使用MassIndexer在第一个搜索请求上对数据进行索引,并且所有后续搜索请求都不会导致重新索引:
private final AtomicBoolean initialized = new AtomicBoolean(false);
...
public FullTextQuery buildTransactionSearchQuery(SearchRequestDTO request) {
final FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();
final Query expression = buildTransactionSearchExpression(request.getFilter(), fullTextEntityManager);
final FullTextQuery query = fullTextEntityManager.createFullTextQuery(expression, Transaction.class);
return query;
}
...
private FullTextEntityManager getFullTextEntityManager() {
final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
if (initialized.get()) {
return fullTextEntityManager;
} else {
synchronized (initialized) {
if (!initialized.getAndSet(true)) {
try {
fullTextEntityManager.createIndexer().startAndWait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
return fullTextEntityManager;
}
}
}
2)在后台作业中:
@Scheduled(initialDelay = 1_000, fixedDelay = 5_000)
private void indexAuditValues() {
Instant previousRunTime = ...; // assume data is set
Instant currentTime = ...;
int page = 0;
boolean hasMore = true;
while (hasMore) {
hasMore = hsIndexingService.indexAuditValues(previousRunTime, currentTime, page++);
}
}
@Transactional(readOnly = true)
public boolean indexAuditValues(Instant previousRunTime, Instant currentTime, int page) {
PageRequest pageRequest = return new PageRequest(page, batchSize, Sort.Direction.ASC, AUDIT_VALUE_SORT_COLUMN);
Page<AuditValue> pageResults = auditValueRepository.findByAuditTransactionLastModifiedDateBetween(previousRunTime, currentTime, pageRequest);
FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();
List<AuditValue> content = pageResults.getContent();
content.forEach(fullTextEntityManager::index); // here we do index the data
return pageResults.hasNext();
}
private FullTextEntityManager getFullTextEntityManager() {
return Search.getFullTextEntityManager(entityManager);
}
最近,我们的用户报告说,新数据未出现在搜索页面上,是否可能由于在2个未同步的独立线程中使用了2个FullTextEntityManager
而引起的?如果是,如何解决?
我们使用文件Spring Boot,Hibernate Search,Lucene并将索引存储在文件系统中。
实体用@Indexed
注释,可搜索字段用@Field
注释。
答案 0 :(得分:1)
我不确定这是否是您的问题的一部分,但是无论如何我都会明确:FullTextEntityManager
可以在两个单独的线程中使用,只要您使用的是不同的实体管理器即可。而且,如果您使用的是Spring,则很可能会这样做。所以那里一切都很好。
我在您的设置中看到的主要问题是,这两种方法可能同时执行(如果第一个搜索查询是在第一个计划索引之前或期间发送的)。但是在那种情况下,您宁愿在索引中得到重复的文档,也不愿丢失文档(由于质量索引器的工作方式)。所以我真的不知道怎么了。
我建议不要在查询方法中懒惰地执行质量索引,更重要的是避免在请求线程中等待可能长时间运行的操作(质量索引):这是一个主要的反模式。
理想情况下,仅在重新部署应用程序时(客户不使用该应用程序时)才应建立索引,并在重新启动后重新使用索引。这样一来,您就不必再等待大量索引的请求:当任何人访问该应用程序时,所有内容都已被索引。
但是您没有执行任何操作,因此我认为您有您的理由。如果您真的想在启动时对所有内容重新编制索引,并在大规模索引编制尚未结束的情况下阻止搜索请求,则下面的内容应该更安全。也许并非完美无缺(实际上取决于您的模型:我不知道是否可以更新审核值),但是更安全。
1)在从UI执行目标数据搜索时,阻止请求,直到初始索引结束为止[再一次,这是一个坏主意,但对每个人来说都是这样]。
// Assuming the background job class is named "IndexInitializer"
@Autowired
IndexInitializer indexInitializer;
...
public FullTextQuery buildTransactionSearchQuery(SearchRequestDTO request) {
final FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();
final Query expression = buildTransactionSearchExpression(request.getFilter(), fullTextEntityManager);
final FullTextQuery query = fullTextEntityManager.createFullTextQuery(expression, Transaction.class);
return query;
}
...
private FullTextEntityManager getFullTextEntityManager() {
indexInitializer.awaitInitialIndexing();
return Search.getFullTextEntityManager(entityManager);
}
2)在后台作业中,在第一个刻度上使用质量索引器,并在随后的每个刻度上使用增量索引:
private final CountDownLatch initialIndexingsRemaining = new CountDownLatch(1);
public void awaitInitialIndexing() {
initialIndexingsRemaining.await();
}
@Scheduled(initialDelay = 0, fixedDelay = 5_000)
private void indexAuditValues() {
if (isInitialIndexingDone()) {
doIncrementalIndexing();
} else {
doInitialIndexing();
}
}
private boolean isInitialIndexingDone() {
return initialIndexingsRemaining.await(0, TimeUnit.NANOSECONDS);
}
private void doInitialIndexing() {
// Synchronization is only necessary here if the scheduled method may be called again before the previous execution is over. Not sure it's possible?
synchronized (this) {
if (isInitialIndexingDone()) {
return;
}
try {
fullTextEntityManager.createIndexer().startAndWait();
initialIndexingsRemaining.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private void doIncrementalIndexing() {
Instant previousRunTime = ...; // assume data is set
Instant currentTime = ...;
int page = 0;
boolean hasMore = true;
while (hasMore) {
hasMore = hsIndexingService.indexAuditValues(previousRunTime, currentTime, page++);
}
}
@Transactional(readOnly = true)
public boolean indexAuditValues(Instant previousRunTime, Instant currentTime, int page) {
PageRequest pageRequest = return new PageRequest(page, batchSize, Sort.Direction.ASC, AUDIT_VALUE_SORT_COLUMN);
Page<AuditValue> pageResults = auditValueRepository.findByAuditTransactionLastModifiedDateBetween(previousRunTime, currentTime, pageRequest);
FullTextEntityManager fullTextEntityManager = getFullTextEntityManager();
List<AuditValue> content = pageResults.getContent();
content.forEach(fullTextEntityManager::index); // here we do index the data
return pageResults.hasNext();
}
private FullTextEntityManager getFullTextEntityManager() {
return Search.getFullTextEntityManager(entityManager);
}
另一方面,您也可以使用自动动态索引来代替手动的定期索引编制:当在Hibernate ORM中持久/更新/删除实体时,Hibernate Search将自动更新索引。