Статьи

Поддержка Spring Data для Cassandra 3

Одним из пунктов, который привлек мое внимание после объявления о выпуске нового релиза Spring Data под названием Ingalls, стало то, что Spring Data Cassandra наконец-то поддерживает Cassandra 3+. Поэтому я повторно посетил один из моих старых образцов и попробовал его с более новой версией Cassandra.

Установка Кассандры

Первым шагом является установка локальной версии Cassandra, и я по-прежнему нахожу инструмент ccm выдающимся в том, что он может поднять и разрушить небольшой кластер. Вот команда, которую я запускаю для запуска 3-х узлового кластера на основе Apache Cassandra 3.9.

1
ccm create test -v 3.9 -n 3 -s --vnodes

Создать схемы

Подключитесь к узлу в кластере:

1
2
3
ccm node1 cqlsh
 
CREATE KEYSPACE IF NOT EXISTS sample WITH replication = {'class':'SimpleStrategy', 'replication_factor':1};

Далее мне нужно создать таблицы для хранения данных. Общая рекомендация Cassandra — смоделировать таблицы на основе шаблонов запросов — учитывая это, позвольте мне сначала определить таблицу для хранения основной информации об «отеле»:

1
2
3
4
5
6
7
8
CREATE TABLE IF NOT EXISTS  sample.hotels (
    id UUID,
    name varchar,
    address varchar,
    state varchar,
    zip varchar,
    primary key((id), name)
);

Предполагая, что мне нужно поддерживать два шаблона запросов — поиск отелей на основе, скажем, первой буквы, и поиск отелей по штатам, у меня есть денормализованная таблица «hotels_by_letter» для поддержки поиска по «первой букве»:

1
2
3
4
5
6
7
8
9
CREATE TABLE IF NOT EXISTS  sample.hotels_by_letter (
    first_letter varchar,
    hotel_name varchar,
    hotel_id UUID,
    address varchar,
    state varchar,
    zip varchar,
    primary key((first_letter), hotel_name, hotel_id)
);

И просто для разнообразия материализованное представление hotels_by_state для поддержки поиска по состоянию, в котором находятся отели:

1
2
3
4
5
CREATE MATERIALIZED VIEW sample.hotels_by_state AS
    SELECT id, name, address, state, zip FROM hotels
        WHERE state IS NOT NULL AND id IS NOT NULL AND name IS NOT NULL
    PRIMARY KEY ((state), name, id)
    WITH CLUSTERING ORDER BY (name DESC)

Репозитории кодирования

Со стороны Java, поскольку я сохраняю и запрашиваю простой тип домена с именем «Hotel», он выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
@Table("hotels")
public class Hotel implements Serializable {
    @PrimaryKey
    private UUID id;
    private String name;
    private String address;
    private String state;
    private String zip;
    ...
}

Теперь, чтобы иметь возможность выполнить базовую операцию CRUD для этого объекта, все, что требуется, — это интерфейс репозитория, как показано в следующем коде:

1
2
3
4
5
6
import cass.domain.Hotel;
import org.springframework.data.repository.CrudRepository;
 
import java.util.UUID;
 
public interface HotelRepository extends CrudRepository<Hotel, UUID>, HotelRepositoryCustom {}

Этот репозиторий также наследуется от интерфейса HotelRepositoryCustom, который должен предоставлять пользовательские средства поиска для поддержки поиска по имени и состоянию.

Теперь, чтобы сохранить сущность Hotel, все, что мне нужно сделать, это вызвать метод репозитория:

1
hotelRepository.save(hotel);

Данные в материализованном представлении автоматически синхронизируются и поддерживаются Cassandra, однако данные в таблице hotels_by_letter должны обрабатываться с помощью кода, поэтому у меня есть другой репозиторий, определенный для хранения данных в этой таблице:

1
2
public interface HotelByLetterRepository
        extends CrudRepository<HotelByLetter, HotelByLetterKey>, HotelByLetterRepositoryCustom {}

Пользовательский интерфейс и его реализация призваны облегчить поиск в этой таблице по запросам, основанным на первой букве названия отеля, и реализованы таким образом через
особенность реализации собственного хранилища данных Spring Cassandra.

01
02
03
04
05
06
07
08
09
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
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.cassandra.core.CassandraTemplate;
import org.springframework.stereotype.Repository;
 
import java.util.List;
 
@Repository
public class HotelRepositoryImpl implements HotelRepositoryCustom {
 
    private final CassandraTemplate cassandraTemplate;
 
    @Autowired
    public HotelRepositoryImpl(CassandraTemplate cassandraTemplate) {
        this.cassandraTemplate = cassandraTemplate;
    }
 
    @Override
    public List<Hotel> findByState(String state) {
        Select select = QueryBuilder.select().from("hotels_by_state");
        select.where(QueryBuilder.eq("state", state));
        return this.cassandraTemplate.select(select, Hotel.class);
    }
}
 
@Repository
public class HotelByLetterRepositoryImpl implements HotelByLetterRepositoryCustom {
    private final CassandraTemplate cassandraTemplate;
 
    public HotelByLetterRepositoryImpl(CassandraTemplate cassandraTemplate) {
        this.cassandraTemplate = cassandraTemplate;
    }
 
    @Override
    public List<HotelByLetter> findByFirstLetter(String letter) {
        Select select = QueryBuilder.select().from("hotels_by_letter");
        select.where(QueryBuilder.eq("first_letter", letter));
        return this.cassandraTemplate.select(select, HotelByLetter.class);
    }
 
}

С учетом этих классов репозитория, пользовательских репозиториев, которые обеспечивают поддержку запросов, остальная часть кода должна соединить все вместе, что облегчает автоматическая конфигурация Cassandra Spring Boot .

По сути, это все, что нужно сделать, Spring Data Cassandra упрощает взаимодействие с Cassandra 3+.

Полностью работающий проект, я считаю, гораздо лучший способ познакомиться с этой превосходной библиотекой, и у меня есть такой образец, доступный в моем репозитории github здесь — https://github.com/bijukunjummen/sample-boot-with-cassandra

Ссылка: Поддержка Spring Data для Cassandra 3 от нашего партнера JCG Биджу Кунджуммена в блоге all and sundry.