В интерактивных системах используются множество различных справочников-словарей данных, это различные статусы, коды, наименования и пр., как правило их много и каждый из них не большой. В структуре у них часто бывают общие атрибуты: Код, ИД, Название и др. В прикладном коде много бывает различных поисков, сравнений по Коду, по ИД справочника. Поиски могут носить расширенный характер, например: поиск по ИД, по Коду, получить список по критерию, сортировки и др… И как следствие справочники кэшируют, уменьшая частое обращение к БД. Здесь хочу показать пример как может пригодится для этих целей 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