Boost Spring Boot 3 Search with Hibernate Search & Lucene: Full Guide
This article explains why traditional database search struggles with large data sets, introduces Lucene as a high‑performance full‑text engine, and shows step‑by‑step how to integrate it into Spring Boot 3 using Hibernate Search, custom analyzers, entity mapping, repository extensions, and test cases.
Traditional database queries such as MySQL LIKE or built‑in full‑text indexes become inefficient when data reaches millions of rows, causing full‑table scans and slow response times, while lacking support for synonym expansion or phonetic correction.
High‑concurrency scenarios demand features like tokenization, sorting, highlighting, and scalability that relational databases cannot provide; Lucene offers an inverted index and TF‑IDF algorithm for millisecond‑level responses and supports custom tokenizers like the Chinese IK analyzer.
In Spring Boot, Hibernate Search bridges the gap between Hibernate ORM and Lucene (or Elasticsearch), creating a "store‑index‑query" loop that can reduce database load by over 70%.
Environment
Spring Boot 3.4.2
1. Introduction
The article demonstrates using Hibernate Search as a bridge between Hibernate ORM and full‑text search engines (Lucene or Elasticsearch).
2. Practical Cases
2.1 Add Dependencies
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-mapper-orm</artifactId>
<version>7.2.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.search</groupId>
<artifactId>hibernate-search-backend-lucene</artifactId>
<version>7.2.1.Final</version>
</dependency>
<!-- IK Chinese Analyzer -->
<dependency>
<groupId>com.jianggujin</groupId>
<artifactId>IKAnalyzer-lucene</artifactId>
<version>8.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queries</artifactId>
</exclusion>
</exclusions>
</dependency>2.2 Configuration
spring:
jpa:
properties:
hibernate:
'[search.backend.type]': lucene
'# Local index directory'
'[search.backend.directory.root]': f:/indexes
'# Use IK analyzer'
'[search.backend.analysis.configurer]': com.pack.search.config.IKAnalysisConfigurer2.3 Custom Analyzer
public class IKAnalysisConfigurer implements LuceneAnalysisConfigurer {
public static final String IK = "ik";
private final Analyzer ik = new IKAnalyzer();
@Override
public void configure(LuceneAnalysisConfigurationContext context) {
context.analyzer(IK).instance(ik);
}
}2.4 Entity Definition
@Entity
@Table(name = "t_book")
@Indexed
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@DocumentId
private Long id;
@Column(nullable = false)
@FullTextField(name = "title")
@KeywordField(name = "sort_title", sortable = Sortable.YES)
private String title;
@Column(nullable = false)
@FullTextField(name = "author")
@KeywordField(name = "sort_author", sortable = Sortable.YES)
private String author;
}2.5 Custom Repository Interface
public interface SearchRepository<T, ID extends Serializable> {
List<T> fullTextSearch(String text, int offset, int limit, List<String> fields, String sortBy, SortOrder sortOrder);
List<T> fuzzySearch(String text, int offset, int limit, List<String> fields, String sortBy, SortOrder sortOrder);
List<T> wildcardSearch(String pattern, int offset, int limit, List<String> fields, String sortBy, SortOrder sortOrder);
}2.6 Base Repository
@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, SearchRepository<T, ID> {}
public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
private final EntityManager entityManager;
public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
@Override
public List<T> fullTextSearch(String text, int offset, int limit, List<String> fields, String sortBy, SortOrder sortOrder) {
if (text == null || text.isEmpty()) return Collections.emptyList();
return Search.session(entityManager)
.search(getDomainClass())
.where(f -> f.match().fields(fields.toArray(String[]::new)).matching(text))
.sort(f -> f.field(sortBy).order(sortOrder))
.fetchHits(offset, limit);
}
@Override
public List<T> fuzzySearch(String text, int offset, int limit, List<String> fields, String sortBy, SortOrder sortOrder) {
if (text == null || text.isEmpty()) return Collections.emptyList();
return Search.session(entityManager)
.search(getDomainClass())
.where(f -> {
BooleanPredicateClausesStep<?> steps = f.bool();
for (String field : fields) {
steps = steps.should(f.match().field(field).matching(text).fuzzy(0));
}
return steps;
})
.sort(s -> s.field(sortBy).order(sortOrder))
.fetchHits(offset, limit);
}
@Override
public List<T> wildcardSearch(String pattern, int offset, int limit, List<String> fields, String sortBy, SortOrder sortOrder) {
return Search.session(entityManager)
.search(getDomainClass())
.where(f -> {
BooleanPredicateClausesStep<?> steps = f.bool();
for (String field : fields) {
steps = steps.should(f.wildcard().field(field).matching(pattern));
}
return steps;
})
.sort(s -> s.field(sortBy).order(sortOrder))
.fetchHits(offset, limit);
}
}2.7 Tests
@Test
public void testFullTextSearch() {
bookService.saveBook(new Book("Spring Boot3实战案例200讲", "Pack"));
bookService.saveBook(new Book("Spring全家桶实战案例", "Xg"));
bookService.saveBook(new Book("MySQL从入门到精通", "Jack"));
bookService.saveBook(new Book("MCP开发指南", "张三"));
List<Book> books = bookRepository.fullTextSearch("案例", 0, 10, List.of("title"), "sort_title", SortOrder.DESC);
System.err.println(books);
}
@Test
public void testFuzzySearchByTitleAndAuthor() {
List<Book> books = bookRepository.fuzzySearch("案例", 0, 10, List.of("title", "author"), "sort_title", SortOrder.DESC);
System.err.println(books);
}Running the test creates index files for the Book entity under F:/indexes. The generated index can be inspected with the following screenshots.
Query results for full‑text search:
Query results for fuzzy search:
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
