@Indexed - this identifies that the class will be added to the Lucene index. You can define a specific index by adding the 'index' attribute to the annotation. We're just choosing the simplest, minimal configuration for this example.
In addition to this - you also need to specify which properties on the entity are to be indexed, and how they are to be indexed. For our example we are again going for the default option by just adding an @Field annotation with no extra parameters. We are adding one other annotation to the 'title' field - @Boost - this is just telling Lucene to give more weight to search term matches that appear in this field (than the same term appearing in the description field).
@Entity
18.
@Indexed
19.
public class Book {
20.
21.
@Id
22.
@GeneratedValue
23.
private Long
id
;
24.
25.
@Field
26.
@Boost(value = 1.5f)
27.
private String title;
28.
29.
@Field
30.
@Lob
31.
private String description;
32.
33.
@Field
34.
@Enumerated(EnumType.STRING)
35.
private BookCategory category;
}
public void updateFullTextIndex() throws Exception {
046.
LOG.info(
"Updating Index"
);
047.
getFullTextEntityManager().createIndexer().startAndWait();
048.
}
049.
050.
/**
051.
* Add a Book to the Database.
052.
*/
053.
@Transactional
054.
public Book addBook(Book book) {
055.
LOG.info(
"Adding Book : "
+ book);
056.
em.persist(book);
057.
return
book;
058.
}
059.
060.
/**
061.
* Delete All Books.
062.
*/
063.
@SuppressWarnings(
"unchecked"
)
064.
@Transactional
065.
public void deleteAllBooks() {
066.
067.
LOG.info(
"Delete All Books"
);
068.
069.
Query allBooks = em.createQuery(
"select b from Book b"
);
070.
List&
lt
;Book&
gt
; books = allBooks.getResultList();
071.
072.
// We need to delete individually (rather than a bulk delete) to ensure they are removed
073.
// from the Lucene index correctly
074.
for
(Book b : books) {
075.
em.remove(b);
076.
}
077.
078.
}
079.
080.
@SuppressWarnings(
"unchecked"
)
081.
@Transactional
082.
public void listAllBooks() {
083.
084.
LOG.info(
"List All Books"
);
085.
LOG.info(
"------------------------------------------"
);
086.
087.
Query allBooks = em.createQuery(
"select b from Book b"
);
088.
List&
lt
;Book&
gt
; books = allBooks.getResultList();
089.
090.
for
(Book b : books) {
091.
LOG.info(b.toString());
092.
getFullTextEntityManager().index(b);
093.
}
094.
095.
}
096.
097.
/**
098.
* Search
for
a Book.
099.
*/
100.
@SuppressWarnings(
"unchecked"
)
101.
@Transactional
102.
public List&
lt
;Book&
gt
; search(BookCategory category, String searchString) {
103.
104.
LOG.info(
"------------------------------------------"
);
105.
LOG.info(
"Searching Books in category '"
+ category +
"' for phrase '"
+ searchString +
"'"
);
106.
107.
// Create a Query Builder
108.
QueryBuilder qb = getFullTextEntityManager().getSearchFactory().buildQueryBuilder().forEntity(Book.class).get();
109.
110.
// Create a Lucene Full Text Query
111.
org.apache.lucene.search.Query luceneQuery = qb.bool()
112.
.must(qb.keyword().onFields(
"title"
,
"description"
).matching(searchString).createQuery())
113.
.must(qb.keyword().onField(
"category"
).matching(category).createQuery()).createQuery();
114.
115.
Query fullTextQuery = getFullTextEntityManager().createFullTextQuery(luceneQuery, Book.class);
116.
117.
// Run Query and print out results to console
118.
List&
lt
;Book&
gt
; result = (List&
lt
;Book&
gt
;) fullTextQuery.getResultList();
119.
120.
// Log the Results
121.
LOG.info(
"Found Matching Books :"
+ result.size());
122.
for
(Book b : result) {
123.
LOG.info(
" - "
+ b);
124.
}
125.
126.
return
result;
127.
}
128.
129.
/**
130.
* Convenience method to get Full Test Entity Manager. Protected scope to assist mocking
in
Unit
131.
* Tests.
132.
* @
return
Full Text Entity Manager.
133.
*/
134.
protected FullTextEntityManager getFullTextEntityManager() {
135.
if
(ftem == null) {
136.
ftem = Search.getFullTextEntityManager(em);
137.
}
138.
return
ftem;
139.
}
public void updateFullTextIndex() throws Exception {
046.
LOG.info(
"Updating Index"
);
047.
getFullTextEntityManager().createIndexer().startAndWait();
048.
}
049.
050.
/**
051.
* Add a Book to the Database.
052.
*/
053.
@Transactional
054.
public Book addBook(Book book) {
055.
LOG.info(
"Adding Book : "
+ book);
056.
em.persist(book);
057.
return
book;
058.
}
059.
060.
/**
061.
* Delete All Books.
062.
*/
063.
@SuppressWarnings(
"unchecked"
)
064.
@Transactional
065.
public void deleteAllBooks() {
066.
067.
LOG.info(
"Delete All Books"
);
068.
069.
Query allBooks = em.createQuery(
"select b from Book b"
);
070.
List&
lt
;Book&
gt
; books = allBooks.getResultList();
071.
072.
// We need to delete individually (rather than a bulk delete) to ensure they are removed
073.
// from the Lucene index correctly
074.
for
(Book b : books) {
075.
em.remove(b);
076.
}
077.
078.
}
079.
080.
@SuppressWarnings(
"unchecked"
)
081.
@Transactional
082.
public void listAllBooks() {
083.
084.
LOG.info(
"List All Books"
);
085.
LOG.info(
"------------------------------------------"
);
086.
087.
Query allBooks = em.createQuery(
"select b from Book b"
);
088.
List&
lt
;Book&
gt
; books = allBooks.getResultList();
089.
090.
for
(Book b : books) {
091.
LOG.info(b.toString());
092.
getFullTextEntityManager().index(b);
093.
}
094.
095.
}
096.
097.
/**
098.
* Search
for
a Book.
099.
*/
100.
@SuppressWarnings(
"unchecked"
)
101.
@Transactional
102.
public List&
lt
;Book&
gt
; search(BookCategory category, String searchString) {
103.
104.
LOG.info(
"------------------------------------------"
);
105.
LOG.info(
"Searching Books in category '"
+ category +
"' for phrase '"
+ searchString +
"'"
);
106.
107.
// Create a Query Builder
108.
QueryBuilder qb = getFullTextEntityManager().getSearchFactory().buildQueryBuilder().forEntity(Book.class).get();
109.
110.
// Create a Lucene Full Text Query
111.
org.apache.lucene.search.Query luceneQuery = qb.bool()
112.
.must(qb.keyword().onFields(
"title"
,
"description"
).matching(searchString).createQuery())
113.
.must(qb.keyword().onField(
"category"
).matching(category).createQuery()).createQuery();
114.
115.
Query fullTextQuery = getFullTextEntityManager().createFullTextQuery(luceneQuery, Book.class);
116.
117.
// Run Query and print out results to console
118.
List&
lt
;Book&
gt
; result = (List&
lt
;Book&
gt
;) fullTextQuery.getResultList();
119.
120.
// Log the Results
121.
LOG.info(
"Found Matching Books :"
+ result.size());
122.
for
(Book b : result) {
123.
LOG.info(
" - "
+ b);
124.
}
125.
126.
return
result;
127.
}
128.
129.
/**
130.
* Convenience method to get Full Test Entity Manager. Protected scope to assist mocking
in
Unit
131.
* Tests.
132.
* @
return
Full Text Entity Manager.
133.
*/
134.
protected FullTextEntityManager getFullTextEntityManager() {
135.
if
(ftem == null) {
136.
ftem = Search.getFullTextEntityManager(em);
137.
}
138.
return
ftem;
139.
}
application-context.xml
This is the Spring configuration file. You can see in the JPA Entity Manager configuration the key for 'hibernate.search.default.indexBase' is added to the jpaPropertyMap to tell Lucene where to create the index. We have also externalised the database login credentials to a properties file (as you may wish to change these for different environments), for example by updating the propertyConfigurer to look for and use a different external properties if it finds one on the file system).<
beans
>
<
context:component-scan
base-package
=
"com.cor.demo.jpa"
/>
14.
15.
<!-- Property configuration -->
16.
<
bean
id
=
"propertyConfigurer"
17.
class
=
"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
18.
p:ignoreUnresolvablePlaceholders
=
"true"
p:ignoreResourceNotFound
=
"true"
>
19.
<
property
name
=
"locations"
>
20.
<
list
>
21.
<
value
>classpath:/system.properties</
value
>
22.
</
list
>
23.
</
property
>
24.
</
bean
>
25.
26.
<!-- JPA Entity Manager Factory -->
27.
<
bean
id
=
"entityManagerFactory"
28.
class
=
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
>
29.
<
property
name
=
"dataSource"
ref
=
"dataSource"
/>
30.
<!-- <property name="packagesToScan" value="com.cor.demo.jpa.entity" /> -->
31.
<
property
name
=
"jpaVendorAdapter"
>
32.
<
bean
class
=
"org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
>
33.
<
property
name
=
"showSql"
value
=
"true"
/>
34.
<
property
name
=
"generateDdl"
value
=
"true"
/>
35.
</
bean
>
36.
</
property
>
37.
<
property
name
=
"jpaPropertyMap"
>
38.
<
map
>
39.
<
entry
key
=
"hibernate.hbm2ddl.auto"
value
=
"update"
/>
40.
<
entry
key
=
"hibernate.format_sql"
value
=
"true"
/>
41.
<
entry
key
=
"hibernate.use_sql_comments"
value
=
"false"
/>
42.
<
entry
key
=
"hibernate.show_sql"
value
=
"false"
/>
43.
<
entry
key
=
"hibernate.search.default.indexBase"
value
=
"/var/lucene/indexes"
/>
44.
</
map
>
45.
</
property
>
46.
</
bean
>
47.
48.
<!-- JPA Data Source -->
49.
<
bean
id
=
"dataSource"
50.
class
=
"org.springframework.jdbc.datasource.DriverManagerDataSource"
>
51.
<
property
name
=
"driverClassName"
value
=
"${database.driver}"
/>
52.
<
property
name
=
"url"
value
=
"${database.url}"
/>
53.
<
property
name
=
"username"
value
=
"${database.username}"
/>
54.
<
property
name
=
"password"
value
=
"${database.password}"
/>
55.
</
bean
>
56.
57.
<!-- Transaction Manager -->
58.
<
bean
id
=
"txManager"
class
=
"org.springframework.orm.jpa.JpaTransactionManager"
>
59.
<
property
name
=
"entityManagerFactory"
ref
=
"entityManagerFactory"
/>
60.
</
bean
>
61.
<
tx:annotation-driven
transaction-manager
=
"txManager"
/>
62.
63.
</
beans
>
Testing Using DBUnit
In the project is an example of using DBUnit with Spring to test adding and searching against the database using DBUnit to populate the database with test data, exercise the Book Manager search operations and then clean the database down. This is a great way to test database functionality and can be easily integrated into maven and continuous build environments.Because DBUnit bypasses the standard JPA insertion calls - the data does not get automatically added to the Lucene index. We have a method exposed on the service interface to update the Full Text index 'updateFullTextIndex()' - calling this causes Lucene to update the index with the current data in the database. This can be useful when you are adding search to pre-populated databases to index the existing content.
Read full article from JPA Searching Using Lucene - A Working Example with Spring and DBUnit | Architects Zone
No comments:
Post a Comment