В интерактивных системах используются множество различных справочников-словарей данных, это различные статусы, коды, наименования и пр., как правило их много и каждый из них не большой. В структуре у них часто бывают общие атрибуты: Код, ИД, Название и др. В прикладном коде много бывает различных поисков, сравнений по Коду, по ИД справочника. Поиски могут носить расширенный характер, например: поиск по ИД, по Коду, получить список по критерию, сортировки и др… И как следствие справочники кэшируют, уменьшая частое обращение к БД. Здесь хочу показать пример как может пригодится для этих целей Spring Data Key-Value Repositories. Основная мысль такая, это продвинутый поиск в Key-Value Repositorie и в случае отсутствия объекта, делать поиск через Spring Data Repositories в БД и далее помещать в Key-Value Repositories.
И так в Spring есть KeyValueOperations это похожий на репозиторий Spring Data, но он оперирует понятием Key-Value и размещаем данные в HashMap структуре (про Spring Data repositories писал здесь). Объекты при этом могут быть любого типа, главное что бы был указан ключ.
Здесь ключом является statusId, и специально указан полный путь аннотации, в дальнейшем я буду использовать JPA Entity, а там тоже есть Id, но уже имеющий отношение к БД.
KeyValueOperations имеет похожие методы как и в Spring Data repositories
Так можно указать java конфигурацию KeyValueOperations для Spring bean
Здесь указан класс хранилище словарей — ConcurrentHashMap
И так поскольку я буду работать с JPA Entity словарями, то я подключаю два из них к этому проекту.
Это словарь «Status» и «Card»
Это стандартные сущности которые соотносятся к таблицами в БД, обращаю внимание на две аннотации Id у каждой сущности, одна для JPA, другая для KeyValueOperations
Структура словарей похожа, пример одной из них
Spring Data repositories для них:
А вот и сам пример DictionaryProvider где соединяем Spring Data repositories и KeyValueOperations
В нем установлены авто инжекты для репозиториев и для KeyValueOperations, а далее простая логика (здесь без проверок на null и пр.), ищем в keyValueTemplate словарь, если есть, то возвращаем, иначе через crudRepository извлекаем из БД и размещаем в keyValueTemplate, и отдаем во вне.
Но если бы все это ограничивалось бы только поиском по ключу, то наверно особого ничего нет. А так KeyValueOperations обладает широким спектром CRUD операций, и запросами. Вот например поиск в том же keyValueTemplate, но уже по Коду используя запрос KeyValueQuery.
Причем понятно если ранее я искал по ИД и объект попал в keyValueTemplate, то поиск по коду того же объекта, вернет его уже из keyValueTemplate, к БД обращения не будет. Для описания запроса используется Spring Expression Language.
Примеры тестов:
поиск по ИД
поиск по Коду
Можно получать списки данных через
В запросе можно указать сортировку
Материалы:
spring-data keyvalue
Проект на github
И так в 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
