📜 ⬆️ ⬇️

Organize data retrieval using Spring Data Key-Value Repositories

Interactive systems use many different reference dictionaries of data, these are different statuses, codes, names, etc., as a rule there are many of them and each of them is not large. In the structure, they often have common attributes: Code, ID, Name, etc. In the application code, there are many different searches, comparisons on the Code, on the ID of the directory. Searches can be advanced, for example: search by ID, by Code, get a list by criterion, sorting, etc ... And as a result, directories are cached, reducing the frequent access to the database. Here I want to show an example of how Spring Data Key-Value Repositories can come in handy for this purpose. The basic idea is that this is an advanced search in the Key-Value Repositorie and in the absence of an object, do a search through the Spring Data Repositories in the database and then put it in the Key-Value Repositories.

image

And so in Spring there is a KeyValueOperations that is similar to the Spring Data repository, but it operates on the concept of Key-Value and places the data in the HashMap structure ( about Spring Data repositories wrote here ). Objects can be of any type, the main thing is that the key be specified.

public class Status { @org.springframework.data.annotation.Id private long statusId; private String code; private String name; .... 

Here the statusId is the key, and the full path of the annotation is specifically indicated, in the future I will use the JPA Entity, and there I also have an ID that is already related to the database.
KeyValueOperations has similar methods as in 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); .... . 

So you can specify the java KeyValueOperations configuration for Spring bean

 @SpringBootApplication public class DemoSpringDataApplication { @Bean public KeyValueOperations keyValueTemplate() { return new KeyValueTemplate(keyValueAdapter()); } @Bean public KeyValueAdapter keyValueAdapter() { return new MapKeyValueAdapter(ConcurrentHashMap.class); } 

Here is the class dictionary storage - ConcurrentHashMap

And since I’ll work with JPA Entity dictionaries, I’ll connect two of them to this project.

This is the “Status” and “Card” dictionary.

 @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; } ... 

These are standard entities that relate to tables in the database, drawing attention to two Id annotations for each entity, one for JPA, the other for KeyValueOperations

The structure of the dictionaries is similar, an example of one of them

 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 for them:

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

And here’s the DictionaryProvider example where we connect Spring Data repositories and 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; } .... 

It contains auto injections for repositories and for KeyValueOperations, and then simple logic (here without null checks, etc.), look for a dictionary in the keyValueTemplate, if there is, then return it, otherwise, via crudRepository, we retrieve it from the database and place it in the keyValueTemplate, and return it to on the outside.

But if all this would be limited only to a search by key, then probably there is nothing special. And so KeyValueOperations has a wide range of CRUD operations, and queries. For example, the search is in the same keyValueTemplate, but already by Code using the KeyValueQuery query.

  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(); } 

Moreover, it is clear if earlier I searched by the ID and the object got into the keyValueTemplate, then the search by the code of the same object, returning it already from the keyValueTemplate, will not be addressed to the database. To describe the request, use Spring Expression Language.

Examples of tests:
search by ID

  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()); } 

search by code

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

You can get lists of data through

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

You can specify sorting in the request

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

Materials:

spring-data keyvalue

Project on github

Source: https://habr.com/ru/post/445060/


All Articles