Hibernate Search

Spring Boot + JPA + Hibernate를 사용해 서버를 구성하였다.
search 기능이 필요해 Hibernate Search를 넣었는데 사용법은 아래와 같다.

Step)
1. pom.xml에 hibernate-search-orm 추가.
2. Entity 추가.
3. ②번의 entity searching을 위한 custom repository interface 추가.
4. ②번의 JpaRepository를 만들고 ④번 repository extends.
5. ③번의 repository searching interface implement.
6. ⑤에서 사용할 NameFilterFactory 추가.

1. pom.xml에 hibernate search 추가

 1
 2
 3
 4
 5
 6
 7
<!-- Hibernate Search -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-search-orm</artifactId>
    <version>5.11.1.Final</version>
</dependency>

2. MyEntity Entity 생성

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Indexed
@Entity
@Table(name = "my_entity")
@Getter
@AnalyzerDef(
        name = "emailAnalyzer",
        tokenizer = @TokenizerDef(factory = ClassicTokenizerFactory.class),
        filters = {
                @TokenFilterDef(factory = LowerCaseFilterFactory.class)
        }
)
@FullTextFilterDef(name = "NameFilterFactory", impl = NameFilterFactory.class)
public class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "card_id")
    @SortableField
    private Long id;

    @Column(name = "name")
    @Setter
    @Field
    @SortableField
    private String name;

    @Column(name = "email")
    @Setter
    @Field
    @Analyzer(definition = "emailAnalyzer")
    @SortableField
    private String email;

    @Column(name = "phone")
    @Setter
    @Field
    @SortableField
    private String phone;

    public MyEntity(String name,
                    String email,
                    String phone) {
        this.name = name;
        this.email = email;
        this.phone = phone;
    }
}
설명)
  • @Indexed로 searching 할 entity 추가.
  • @Field로 searching 대상이 될 field 추가.
  • @SortableField로 sort 대상이 될 field 추가.
  • AnalyzerDef로 email searching을 위한 analyzer 추가.
  • FullTextFilterDef로 searching 결과 filtering 할 filter factory 추가.

3. Searching을 위한 Custom Repository 생성

1
2
3
public interface MyEntitySearchRepository {
    List<MyEntity> searchKeyword(String name, String keyword, Pageable pageable);
}

설명)
  • name은 filtering할 대상.
  • keyword는 searching keyword.
  • pageable은 spring에서 paging에 사용하는 Pageable 객체.

4. JpaRepository 생성

1
2
public interface MyEntityRepository extends JpaRepository<MyEntity, Long>, MyEntitySearchRepository {
}
설명)
  • JpaRepository 생성하면서 searching을 위한 interface extends.
  • Custom repository로 만든 이유는 @PersistenceContext로 EntityManager 가져오기 위해.

5. Searching Interface Implement

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Component(value = "myEntitySearchRepository")
public class MyEntitySearchRepositoryImpl implements MyEntitySearchRepository {
    @PersistenceContext
    private EntityManager entityManager;

    public void initializeHibernateSearch() {
        try {
            FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
            fullTextEntityManager.createIndexer().startAndWait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public List<MyEntity> searchKeyword(String name, String keyword, Pageable pageable) {
        // create lucene search index.
        initializeHibernateSearch();

        FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
        QueryBuilder qBuilder = fullTextEntityManager.getSearchFactory()
                .buildQueryBuilder()
                .forEntity(MyEntity.class)
                .get();

        // Lucene query by Hibernate DSL
        Query luceneQuery = qBuilder
                .keyword()
                .wildcard()
                .onFields("email", "phone")
                .matching(keyword)
                .createQuery();

        FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, MyEntity.class);

        // Search filter by name
        fullTextQuery.enableFullTextFilter("NameFilterFactory").setParameter("name", name);

        // pagination
        // convert to start element
        int page = pageable.getPageNumber() * pageable.getPageSize();
        int size = pageable.getPageSize();
        fullTextQuery.setFirstResult(page);
        fullTextQuery.setMaxResults(size);

        // Sort
        if (!pageable.getSort().isUnsorted()) {
            org.apache.lucene.search.Sort sort = new org.apache.lucene.search.Sort();
            Iterator<Sort.Order> orders = pageable.getSort().iterator();
            Sort.Order o = orders.next();
            if (o.getDirection().equals(Sort.Direction.ASC)) {
                sort.setSort(new SortField(o.getProperty(), SortField.Type.STRING, false));
            } else {
                sort.setSort(new SortField(o.getProperty(), SortField.Type.STRING, true));
            }
            fullTextQuery.setSort(sort);
        }

        // If want to get total count.
        // int total = fullTextQuery.getResultSize();

        return fullTextQuery.getResultList();
    }
}
설명)
  • initializeHibernateSearch()는 Lucene indexing을 위한 함수.
  • hibernate의 CRUD는 자동 indexing 되기 때문에 기존의 data가 있는 경우에 실행하면 됨.
  • 위 코드는 searching 할때마다 indexing이 호출 되는데 그래도 되나?
  • keyword searching하고 결과를 name으로 filtering.
  • paging과 sorting도 처리 되어 있음.

6. NameFilterFactory 생성

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class NameFilterFactory {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Factory
    public Query getFilter() {
        return new TermQuery(new Term("name", name));
    }
}
설명)
  • searching에서 filtering할 name field factory.

댓글

이 블로그의 인기 게시물

[Protocol] WIEGAND 통신

Orange for Oracle에서 한글 깨짐 해결책

[URL] 대소문자를 구분하나?