Статьи

Начало работы с Spring Data Solr

Spring Data Solr является расширением проекта Spring Data, целью которого является упрощение использования Apache Solr в приложениях Spring. Обратите внимание, что это не введение в Spring (Data) или Solr. Я предполагаю, что у вас есть хотя бы некоторое базовое понимание обеих технологий. В следующем посте я покажу, как вы можете использовать репозитории Spring Data для доступа к функциям Solr в приложениях Spring.

конфигурация

Для начала нам нужен работающий сервер Solr. Для простоты мы будем использовать пример конфигурации, которая поставляется с текущей версией Solr (4.5.1 на момент написания статьи) и описана в официальном руководстве по Solr . Поэтому нам нужно только скачать Solr, распаковать его в каталог по нашему выбору и затем запустить java -jar start.jar из каталога <solr home> / example.

Теперь давайте перейдем к нашему демонстрационному приложению и добавим зависимость Spring Data Solr с помощью maven:

1
2
3
4
5
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-solr</artifactId>
  <version>1.0.0.RELEASE</version>
</dependency>

В этом примере я использую Spring Boot для настройки небольшого примера приложения Spring. Для этого я использую следующие зависимости Spring Boot и родительский pom Spring Boot:

1
2
3
4
5
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>0.5.0.BUILD-SNAPSHOT</version>
</parent>
1
2
3
4
5
6
7
8
9
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>

Не беспокойтесь, если вы еще не использовали Spring Boot. Эти зависимости в основном служат ярлыком для общих (Spring) зависимостей и немного упрощают настройку. Если вы хотите интегрировать Spring Data Solr в существующее приложение Spring, вы можете пропустить зависимости Spring Boot.

Конфигурация Spring bean довольно проста, нам нужно определить только два bean-компонента:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@ComponentScan
@EnableSolrRepositories("com.mscharhag.solr.repository")
public class Application {
 
  @Bean
  public SolrServer solrServer() {
    return new HttpSolrServer("http://localhost:8983/solr");
  }
 
  @Bean
  public SolrTemplate solrTemplate(SolrServer server) throws Exception {
    return new SolrTemplate(server);
  }
}

Компонент solrServer используется для подключения к работающему экземпляру Solr. Поскольку Spring Data Solr использует Solrj, мы создаем экземпляр Solrj HttpSolrServer . Также было бы возможно использовать встроенный сервер Solr с помощью EmbeddedSolrServer . SolrTemplate предоставляет общие функциональные возможности для работы с Solr (аналогично JdbcTemplate Spring). Bean-компонент solrTemplate необходим для создания репозиториев Solr. Обратите также внимание на аннотацию @EnableSolrRepositories. С помощью этой аннотации мы сообщаем Spring Data Solr искать в указанном пакете репозитории Solr.

Создание документа

Прежде чем мы сможем запросить Solr, мы должны добавить документы в индекс. Чтобы определить документ, мы создаем POJO и добавляем к нему аннотации Solrj. В этом примере мы будем использовать простой класс Book в качестве документа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class Book {
 
  @Field
  private String id;
 
  @Field
  private String name;
 
  @Field
  private String description;
 
  @Field("categories_txt")
  private List<Category> categories;
 
  // getters/setters
}
1
2
3
public enum Category {
  EDUCATION, HISTORY, HUMOR, TECHNOLOGY, ROMANCE, ADVENTURE
}

Каждая книга имеет уникальный идентификатор, имя, описание и относится к одной или нескольким категориям. Обратите внимание, что Solr требует уникальный идентификатор типа String для каждого документа по умолчанию. Поля, которые должны быть добавлены в индекс Solr, помечены аннотацией Solrj @Field . По умолчанию Solrj пытается сопоставить имена полей документа с полями Solr с тем же именем. Пример конфигурации Solr уже определяет поля Solr с именами id, name и description, поэтому нет необходимости добавлять эти поля в конфигурацию Solr.

Если вы хотите изменить определения полей Solr, вы можете найти пример файла конфигурации в <solr home> /example/solr/collection1/conf/schema.xml. В этом файле вы должны найти следующие определения полей:

1
2
3
<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" /> 
<field name="name" type="text_general" indexed="true" stored="true" />
<field name="description" type="text_general" indexed="true" stored="true"/>

В общем, название будет лучшим атрибутом для книги, чем имя. Однако, используя имя, мы можем использовать конфигурацию поля Solr по умолчанию. Поэтому для простоты я выбрал имя вместо названия.

Для категорий мы должны определить имя поля вручную, используя аннотацию @Field: Categories_txt. Это соответствует динамическому полю с именем * _txt из примера Solr. Это определение поля также можно найти в schema.xml:

1
<dynamicField name="*_txt" type="text_general"   indexed="true"  stored="true" multiValued="true"/>

Создание хранилища

Spring Data использует репозитории для упрощения использования различных технологий доступа к данным. Репозиторий — это в основном интерфейс, реализация которого динамически генерируется Spring Data при запуске приложения. Сгенерированная реализация основана на соглашениях об именах, используемых в интерфейсе репозитория. Если это плохо для вас, я рекомендую прочитать Работа с репозиториями данных Spring .

Spring Data Solr использует тот же подход. Мы используем соглашения об именах и аннотации внутри интерфейсов, чтобы определить методы, необходимые для доступа к функциям Solr. Мы начнем с простого репозитория, который содержит только один метод (мы добавим больше позже):

1
2
3
4
5
public interface BookRepository extends SolrCrudRepository<Book, String> {
 
  List<Book> findByName(String name);
 
}

Мы получаем некоторые распространенные методы, такие как save (), findAll (), delete () или count () в хранилище, расширяя SolrCrudRepository. С помощью определения метода интерфейса findByName (имя String) мы сообщаем Spring Data Solr создать реализацию метода, которая запрашивает Solr для получения списка книг. Названия книг в этом списке должны соответствовать переданному параметру.

Реализацию репозитория можно внедрить в другие классы, используя функциональность DI Spring. В этом примере мы внедряем репозиторий в простой тест JUnit:

1
2
3
4
5
6
7
8
9
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = Application.class, loader=SpringApplicationContextLoader.class)
public class BookRepositoryTests {
 
  @Autowired
  private BookRepository bookRepository;
 
  ...
}

Добавление документа в Solr

Теперь пришло время добавить несколько книг в Solr. Используя наш репозиторий, это очень простая работа:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private void addBookToIndex(String name, String description, Category... categories) {
  Book book = new Book();
  book.setName(name);
  book.setDescription(description);
  book.setCategories(Arrays.asList(categories));
  book.setId(UUID.randomUUID().toString());
  bookRepository.save(book);
}
 
private void createSampleData() {
  addBookToIndex("Treasure Island""Best seller by R.L.S.", Category.ADVENTURE);
  addBookToIndex("The Pirate Island""Oh noes, the pirates are coming!", Category.ADVENTURE, Category.HUMOR);
  ...
}

Добавление пагинации и бустинга

Предположим, у нас есть приложение, в котором пользователи могут искать книги. Нам нужно найти книги, чье имя или описание соответствуют поисковому запросу, заданному пользователем. По соображениям производительности мы хотим добавить какую-то нумерацию страниц, которая показывает только 10 результатов поиска для пользователя.

Давайте создадим новый метод в нашем интерфейсе репозитория для этого:

1
Page<Book> findByNameOrDescription(@Boost(2) String name, String description, Pageable pageable);

Имя метода findByNameOrDescription указывает Spring Data Solr запрашивать объекты книги, чье имя или описание соответствуют переданным параметрам. Для поддержки нумерации страниц мы добавили параметр Pageable и изменили тип возвращаемого значения с List <Book> на Page <Book>. Добавляя аннотацию @Boost к параметру name, мы добавляем книги, чье имя соответствует параметру поиска. Это имеет смысл, потому что эти книги обычно более интересны для пользователя.

Если мы теперь хотим запросить первую страницу, содержащую 10 элементов, нам просто нужно сделать:

1
2
Page<Book> booksPage = bookRepository.findByNameOrDescription
(searchString, searchString, new PageRequest(010));

Помимо первых 10 результатов поиска, страница <Book> предоставляет несколько полезных методов для построения функциональности нумерации страниц:

1
2
3
4
5
6
7
8
booksPage.getContent()       // get a list of (max) 10 books
booksPage.getTotalElements() // total number of elements (can be >10)
booksPage.getTotalPages()    // total number of pages
booksPage.getNumber()        // current page number
booksPage.isFirstPage()      // true if this is the first page
booksPage.hasNextPage()      // true if another page is available
booksPage.nextPageable()     // the pageable for requesting the next page
...

огранка

Всякий раз, когда пользователь ищет название книги, мы хотим показать ему, сколько книг, соответствующих данному параметру запроса, доступно в разных категориях. Эта функция называется фасетным поиском и напрямую поддерживается Spring Data Solr. Нам просто нужно добавить еще один метод в наш интерфейс хранилища:

1
2
3
@Query("name:?0")
@Facet(fields = { "categories_txt" }, limit = 5)
FacetPage<Book> findByNameAndFacetOnCategories(String name, Pageable page);

На этот раз запрос будет получен из аннотации @Query (содержащей запрос Solr) вместо имени метода. С помощью аннотации @Facet мы сообщаем Spring Data Solr о фасетных книгах по категориям и возвращаем первые пять фасетов.

Также было бы возможно удалить аннотацию @Query и изменить имя метода на findByName для того же эффекта. Небольшой недостаток этого подхода заключается в том, что для вызывающей стороны не очевидно, что этот метод хранилища выполняет фасетирование. Кроме того, сигнатура метода может конфликтовать с другими методами, которые ищут книги по имени.

Использование:

01
02
03
04
05
06
07
08
09
10
11
12
13
FacetPage<Book> booksFacetPage = bookRepository.findByNameAndFacetOnCategories(bookName, new PageRequest(010));
 
booksFacetPage.getContent(); // the first 10 books
 
for (Page<? extends FacetEntry> page : booksFacetPage.getAllFacets()) {
  for (FacetEntry facetEntry : page.getContent()) {
    String categoryName = facetEntry.getValue();  // name of the category
    long count = facetEntry.getValueCount();      // number of books in this category
 
    // convert the category name back to an enum
    Category category = Category.valueOf(categoryName.toUpperCase());
  }
}

Обратите внимание, что booksFacetPage.getAllFacets () возвращает коллекцию страниц FacetEntry. Это связано с тем, что аннотация @Facet позволяет одновременно обрабатывать несколько полей. Каждая страница FacetPage содержит макс. пять FacetEntries (определяется атрибутом limit @Facet).

Выделив

Часто бывает полезно выделить вхождения результатов поиска в список результатов поиска (как это делают Google или Bing). Это может быть достигнуто с помощью функции выделения (Spring Data) Solr.

Давайте добавим еще один метод репозитория:

1
2
@Highlight(prefix = "<highlight>", postfix = "</highlight>")
HighlightPage<Book> findByDescription(String description, Pageable pageable);

Аннотация @Highlight сообщает Solr, чтобы выделить вхождения искомого описания.

Использование:

01
02
03
04
05
06
07
08
09
10
11
12
13
HighlightPage<Book> booksHighlightPage = bookRepository.findByDescription(description, new PageRequest(010));
 
booksHighlightPage.getContent(); // first 10 books
 
for (HighlightEntry<Book> he : booksHighlightPage.getHighlighted()) {
  // A HighlightEntry belongs to an Entity (Book) and may have multiple highlighted fields (description)
  for (Highlight highlight : he.getHighlights()) {
    // Each highlight might have multiple occurrences within the description
    for (String snipplet : highlight.getSnipplets()) {
      // snipplet contains the highlighted text
    }
  }
}

Если вы используете этот метод репозитория для запроса книг, описание которых содержит строку Остров сокровищ, фрагмент может выглядеть следующим образом:

1
<highlight>Treasure Island</highlight> is a tale of pirates and villains, maps, treasure and shipwreck, and is perhaps one of the best adventure story ever written.

В этом случае Остров сокровищ находится в начале описания и выделяется префиксом и постфиксом, определенными в аннотации @Highlight. Эта дополнительная разметка может использоваться для маркировки вхождений запроса, когда результаты поиска отображаются пользователю.

Вывод

Spring Data Solr предоставляет очень простой способ интеграции Solr в приложения Spring. С абстракцией репозитория он следует тому же принципу проектирования, что и большинство других проектов Spring Data. Единственным небольшим недостатком, с которым я столкнулся во время игры с Spring Data Solr, была документация, которую можно было улучшить здесь и там.

  • Вы можете найти полный исходный код для этого примера на GitHub .

Ссылка: Начало работы с Spring Data Solr от нашего партнера по JCG Майкла Шаргага в блоге mscharhag, Programming and Stuff .