How to extend Spring with your Repository type using Infinispan as an example

Why write about it?

This is my first article, in which I will try to describe the practical experience I gained with the Spring Repository under the hood of the framework. I did not find ready-made articles about this topic on the Internet either in Russian or in English, there were only a few source repositories on github, well, the source code of Spring itself. Therefore, I decided, why not write, suddenly the topic of writing your own types of repositories for Spring is relevant for someone else.





I will not consider programming for Infinispan in detail, the implementation details can always be found in the source code specified at the end of the article. The main emphasis is on the pairing of the Spring Boot Repository mechanism and a new type of repository.





How it all began

While working on one of the projects, one of the architect had the idea that you can write your own types of repositories by analogy, as it is done in different Spring modules (for example, JPARepository, KeyValueRepository, CassandraRepository, etc.). As a trial implementation, we decided to choose working with data via Infinispan .





Naturally, architects are busy people, so the Java developer was assigned to implement the idea, i.e. to me.





When I started working on the topic on the Internet, Google stubbornly gave out almost one article about how wonderful it is to use JPARepository in all kinds with trivial examples. There was even less information on KeyValueRepository. StackOverFlow has sad unanswered questions on a similar topic. There is nothing to do, I had to go into the Spring sources.





Infinispan

If we talk briefly about Infinispan, then this is just a distributed data storage in the form of a key-value, and all this is constantly cached in memory. We overload Infinispan, the data is all zeroed.





, - KeyValueRepository, , Spring. , Infinispan ( Hazelcast, ), , KeyValueRepository ConcurrentHashMap.





Spring - EnableMapRepositories.





@SpringBootApplication
@EnableMapRepositories("my.person.package.for.entities")
public class Application {

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

}
      
      



EnableInfinispanRepositories.





, , map infinispan, , .





EnableInfinispanRepositories
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(InfinispanRepositoriesRegistrar.class)
public @interface EnableInfinispanRepositories {

  String[] value() default {};

  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};

  ComponentScan.Filter[] excludeFilters() default {};

  ComponentScan.Filter[] includeFilters() default {};

  String repositoryImplementationPostfix() default "Impl";

  String namedQueriesLocation() default "";

  QueryLookupStrategy.Key queryLookupStrategy() default 
    QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND;

  Class<?> repositoryFactoryBeanClass() default 
    KeyValueRepositoryFactoryBean.class;

  Class<?> repositoryBaseClass() default 
    DefaultRepositoryBaseClass.class;

  String keyValueTemplateRef() default "infinispanKeyValueTemplate";

  boolean considerNestedRepositories() default false;

}
      
      







EnableMapRepositories, , , .





@Import(MapRepositoriesRegistrar.class)
public @interface EnableMapRepositories {
}
      
      



MapRepositoriesRegistar.





public class MapRepositoriesRegistrar extends 
  RepositoryBeanDefinitionRegistrarSupport {

  @Override
  protected Class<? extends Annotation> getAnnotation() {
    return EnableMapRepositories.class;
  }
  
  @Override
  protected RepositoryConfigurationExtension getExtension() {
    return new MapRepositoryConfigurationExtension();
  }
}
      
      



. Registar , . , .





InfinispaRepositoriesRegistar.
@NoArgsConstructor
public class InfinispanRepositoriesRegistrar extends 
  RepositoryBeanDefinitionRegistrarSupport {

  @Override
  protected Class<? extends Annotation> getAnnotation() {
    return EnableInfinispanRepositories.class;
  }

  @Override
  protected RepositoryConfigurationExtension getExtension() {
    return new InfinispanRepositoryConfigurationExtension();
  }
}
      
      







, .





public class MapRepositoryConfigurationExtension extends 
  KeyValueRepositoryConfigurationExtension {

  @Override
  public String getModuleName() {
    return "Map";
  }

  @Override
  protected String getModulePrefix() {
    return "map";
  }

  @Override
  protected String getDefaultKeyValueTemplateRef() {
    return "mapKeyValueTemplate";
  }

  @Override
  protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(RepositoryConfigurationSource configurationSource) {
    BeanDefinitionBuilder adapterBuilder = BeanDefinitionBuilder
      .rootBeanDefinition(MapKeyValueAdapter.class);
    adapterBuilder.addConstructorArgValue(
      getMapTypeToUse(configurationSource));
    BeanDefinitionBuilder builder = BeanDefinitionBuilder
      .rootBeanDefinition(KeyValueTemplate.class);
    ...
  }
  ...
}
      
      



MapKeyValueAdapter , HashMap. KeyValueTemplate .





Infinispan, ConfigurationExtension, , // , Infinispan.





InfinispanRepositoriesConfigurationExtension
@NoArgsConstructor
public class InfinispanRepositoryConfigurationExtension 
  extends KeyValueRepositoryConfigurationExtension {

  @Override
  public String getModuleName() {
    return "Infinispan";
  }

  @Override
  protected String getModulePrefix() {
    return "infinispan";
  }

  @Override
  protected String getDefaultKeyValueTemplateRef() {
    return "infinispanKeyValueTemplate";
  }

  @Override
  protected Collection<Class<?>> getIdentifyingTypes() {
    return Collections.singleton(InfinispanRepository.class);
  }

  @Override
  protected AbstractBeanDefinition getDefaultKeyValueTemplateBeanDefinition(RepositoryConfigurationSource configurationSource) {
    RootBeanDefinition infinispanKeyValueAdapterDefinition = 
      new RootBeanDefinition(InfinispanKeyValueAdapter.class);
    RootBeanDefinition keyValueTemplateDefinition = 
      new RootBeanDefinition(KeyValueTemplate.class);
    ConstructorArgumentValues constructorArgumentValuesForKeyValueTemplate = new ConstructorArgumentValues();
    constructorArgumentValuesForKeyValueTemplate
      .addGenericArgumentValue(infinispanKeyValueAdapterDefinition);
    keyValueTemplateDefinition.setConstructorArgumentValues(
      constructorArgumentValuesForKeyValueTemplate);
    return keyValueTemplateDefinition;
  }
}
      
      







ConfigurationExtension getIdentifyingTypes(), (. ).





@NoRepositoryBean
public interface InfinispanRepository <T, ID> extends 
  PagingAndSortingRepository<T, ID> {
}
      
      



, KeyValueTemplate, .





@Configuration
public class InfinispanConfiguration extends CachingConfigurerSupport {

  @Autowired
  private ApplicationContext applicationContext;

  @Bean
  public InfinispanKeyValueAdapter getInfinispanAdapter() {
    return new InfinispanKeyValueAdapter(
      applicationContext.getBean(CacheManager.class)
    );
  }

  @Bean("infinispanKeyValueTemplate")
  public KeyValueTemplate getInfinispanKeyValueTemplate() {
    return new KeyValueTemplate(getInfinispanAdapter());
  }
}
      
      



.





, , Spring- , , , .





Summary

Having written only 6 of our classes, we got a new type of repository that can work with Infinispan as a data store. And this new type of repository works very much like standard Spring repositories.





The complete source kit can be found on my github .





Spring Data KeyValue sources can also be seen on github .





If you have constructive comments on this implementation, then write in the comments, or you can make a pull request in the original project.








All Articles