Organizing data searches using Spring Data Key-Value Repositories

    Interactive systems use a lot of different directories, data dictionaries, these are various 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 by Code, by reference ID. Searches can be extended, for example: search by ID, by Code, get a list by criterion, sort, etc. ... And as a result, directories are cached, reducing frequent access to the database. Here I want to show an example of how Spring Data Key-Value Repositories can be useful for these purposes. The main idea is this: 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 place it in the Key-Value Repositories.

    image

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

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

    Here the key is statusId, and the full path of the annotation is specifically indicated, in the future I will use JPA Entity, and there is also an Id there, but already related to the database.
    KeyValueOperations has similar methods as in Spring Data repositories

    interface KeyValueOperations {
         T insert(T objectToInsert);                               
        void update(Object objectToUpdate);                           
        void delete(Class type);                                   
         T findById(Object id, Class type);                     
         Iterable find(KeyValueQuery query, Class type);   
         .... др.
        

    So you can specify 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);
    	}
    

    The dictionary storage class ConcurrentHashMap is specified here.

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

    This is a dictionary of “Status” and “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;
        }
    ...
    

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

    The structure of 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 {
    }
    @Repository
    public interface StatusCrudRepository extends CrudRepository {
    }
    

    And here is the DictionaryProvider example itself where we connect Spring Data repositories and KeyValueOperations

    @Service
    public class DictionaryProvider {
        private static Logger logger = LoggerFactory.getLogger(DictionaryProvider.class);
        private Map 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  Optional dictionaryById(Class clazz, long id) {
            Optional 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;
        }
       ....
    

    Auto injects are installed in it for repositories and for KeyValueOperations, and then simple logic (here, without checking for null, etc.), we look in the keyValueTemplate dictionary, if there is, then we return, otherwise we extract from the database via crudRepository and place in keyValueTemplate, and give out.

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

        public  Optional dictionaryByCode(Class clazz, String code) {
            KeyValueQuery query = new KeyValueQuery<>(String.format("code == '%s'", code));
            Iterable iterable = keyValueTemplate.find(query, clazz);
            Iterator iterator = iterable.iterator();
            if (iterator.hasNext()) {
                return Optional.of(iterator.next());
            }
            return Optional.empty();
        }
    

    And it’s understandable, if earlier I searched by ID and the object got into keyValueTemplate, then searching by the code of the same object will return it already from keyValueTemplate, there will be no access to the database. Spring Expression Language is used to describe the request.

    Test Examples:
    ID Search

    	private void find() {
    		Optional status = dictionaryProvider.dictionaryById(Status.class, 1L);
    		Assert.assertTrue(status.isPresent());
    		Optional card = dictionaryProvider.dictionaryById(Card.class, 100L);
    		Assert.assertTrue(card.isPresent());
    	}
    

    Search by Code

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

    You can get lists of data through

     Iterable find(KeyValueQuery query, Class type);

    You can specify sorting in the request

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

    Materials:

    spring-data keyvalue

    github project

    Also popular now: