Как стать автором
Обновить

Организация поиска данных с применением Spring Data Key-Value Repositories

Время на прочтение4 мин
Количество просмотров7.6K
В интерактивных системах используются множество различных справочников-словарей данных, это различные статусы, коды, наименования и пр., как правило их много и каждый из них не большой. В структуре у них часто бывают общие атрибуты: Код, ИД, Название и др. В прикладном коде много бывает различных поисков, сравнений по Коду, по ИД справочника. Поиски могут носить расширенный характер, например: поиск по ИД, по Коду, получить список по критерию, сортировки и др… И как следствие справочники кэшируют, уменьшая частое обращение к БД. Здесь хочу показать пример как может пригодится для этих целей Spring Data Key-Value Repositories. Основная мысль такая, это продвинутый поиск в Key-Value Repositorie и в случае отсутствия объекта, делать поиск через Spring Data Repositories в БД и далее помещать в Key-Value Repositories.

image

И так в Spring есть KeyValueOperations это похожий на репозиторий Spring Data, но он оперирует понятием Key-Value и размещаем данные в HashMap структуре (про Spring Data repositories писал здесь). Объекты при этом могут быть любого типа, главное что бы был указан ключ.

public class Status {

    @org.springframework.data.annotation.Id
    private long statusId;

    private String code;
    private String name;
    ....

Здесь ключом является statusId, и специально указан полный путь аннотации, в дальнейшем я буду использовать JPA Entity, а там тоже есть Id, но уже имеющий отношение к БД.
KeyValueOperations имеет похожие методы как и в Spring Data repositories

interface KeyValueOperations {

    <T> T insert(T objectToInsert);                               

    void update(Object objectToUpdate);                           

    void delete(Class<?> type);                                   

    <T> T findById(Object id, Class<T> type);                     

    <T> Iterable<T> find(KeyValueQuery<?> query, Class<T> type);   
     .... др.
    

Так можно указать java конфигурацию KeyValueOperations для Spring bean

@SpringBootApplication
public class DemoSpringDataApplication {

	@Bean
	public KeyValueOperations keyValueTemplate() {
		return new KeyValueTemplate(keyValueAdapter());
	}

	@Bean
	public KeyValueAdapter keyValueAdapter() {
		return new MapKeyValueAdapter(ConcurrentHashMap.class);
	}


Здесь указан класс хранилище словарей — ConcurrentHashMap

И так поскольку я буду работать с JPA Entity словарями, то я подключаю два из них к этому проекту.

Это словарь «Status» и «Card»

@Entity
public class Status {

    @org.springframework.data.annotation.Id
    private long statusId;
    private String code;
    private String name;

    @Id
    @Column(name = "STATUS_ID")
    public long getStatusId() {
        return statusId;
    }
....

@Entity
public class Card {

    @org.springframework.data.annotation.Id
    private long cardId;
    private String code;
    private String name;

    @Id
    @Column(name = "CARD_ID")
    public long getCardId() {
        return cardId;
    }
...

Это стандартные сущности которые соотносятся к таблицами в БД, обращаю внимание на две аннотации Id у каждой сущности, одна для JPA, другая для KeyValueOperations

Структура словарей похожа, пример одной из них

create table STATUS
(
  status_id NUMBER not null,
  code      VARCHAR2(20) not null,
  name      VARCHAR2(50) not null
);
-- Create/Recreate primary, unique and foreign key constraints 
alter table STATUS add constraint STATUS_PK primary key (STATUS_ID)


Spring Data repositories для них:

@Repository
public interface CardCrudRepository extends CrudRepository<Card, Long> {
}

@Repository
public interface StatusCrudRepository extends CrudRepository<Status, Long> {
}


А вот и сам пример DictionaryProvider где соединяем Spring Data repositories и KeyValueOperations

@Service
public class DictionaryProvider {

    private static Logger logger = LoggerFactory.getLogger(DictionaryProvider.class);

    private Map<Class, CrudRepository> repositoryMap = new HashMap<>();

    @Autowired
    private KeyValueOperations keyValueTemplate;

    @Autowired
    private StatusCrudRepository statusRepository;
    @Autowired
    private CardCrudRepository cardRepository;

    @PostConstruct
    public void post() {
        repositoryMap.put(Status.class, statusRepository);
        repositoryMap.put(Card.class, cardRepository);
    }

    public <T> Optional<T> dictionaryById(Class<T> clazz, long id) {
        Optional<T> optDictionary = keyValueTemplate.findById(id, clazz);
        if (optDictionary.isPresent()) {
            logger.info("Dictionary {} found in keyValueTemplate", optDictionary.get());
            return optDictionary;
        }

        CrudRepository crudRepository = repositoryMap.get(clazz);
        optDictionary = crudRepository.findById(id);
        keyValueTemplate.insert(optDictionary.get());
        logger.info("Dictionary {} insert in keyValueTemplate", optDictionary.get());

        return optDictionary;
    }
   ....

В нем установлены авто инжекты для репозиториев и для KeyValueOperations, а далее простая логика (здесь без проверок на null и пр.), ищем в keyValueTemplate словарь, если есть, то возвращаем, иначе через crudRepository извлекаем из БД и размещаем в keyValueTemplate, и отдаем во вне.

Но если бы все это ограничивалось бы только поиском по ключу, то наверно особого ничего нет. А так KeyValueOperations обладает широким спектром CRUD операций, и запросами. Вот например поиск в том же keyValueTemplate, но уже по Коду используя запрос KeyValueQuery.

    public <T> Optional<T> dictionaryByCode(Class<T> clazz, String code) {
        KeyValueQuery<String> query = new KeyValueQuery<>(String.format("code == '%s'", code));
        Iterable<T> iterable = keyValueTemplate.find(query, clazz);

        Iterator<T> iterator = iterable.iterator();
        if (iterator.hasNext()) {
            return Optional.of(iterator.next());
        }
        return Optional.empty();
    }

Причем понятно если ранее я искал по ИД и объект попал в keyValueTemplate, то поиск по коду того же объекта, вернет его уже из keyValueTemplate, к БД обращения не будет. Для описания запроса используется Spring Expression Language.

Примеры тестов:
поиск по ИД

	private void find() {
		Optional<Status> status = dictionaryProvider.dictionaryById(Status.class, 1L);
		Assert.assertTrue(status.isPresent());

		Optional<Card> card = dictionaryProvider.dictionaryById(Card.class, 100L);
		Assert.assertTrue(card.isPresent());
	}


поиск по Коду

	private void findByCode() {
		Optional<Card> card = dictionaryProvider.dictionaryByCode(Card.class, "VISA");
		Assert.assertTrue(card.isPresent());
	}

Можно получать списки данных через

<T> Iterable<T> find(KeyValueQuery<?> query, Class<T> type);

В запросе можно указать сортировку

query.setSort(Sort.by(DESC, "name"));

Материалы:

spring-data keyvalue

Проект на github
Теги:
Хабы:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

Публикации