5

I'm testing Redis with spring-data-redis using repositories like this:

public interface CreditCardRepository extends CrudRepository<CreditCard, String>{
    List<CreditCard> findByIssuer(String issuer);
    List<CreditCard> findByCreditNetwork(String creditNetwork);
    List<CreditCard> findByCreditNetworkAndIssuer(String creditNetwork, String issuer);
}

Above methods will query over Redis structures like:

creditcard:creditNetwork:mastercard
creditcard:creditNetwork:visa
creditcard:issuer:company1
creditcard:issuer:company2

Right now my CreditCard object contains two attributes (issuer, network and the id), so it's easy to search objects like this:

private List<CreditCard> searchCardFromCache(CreditCardGetReq req) {
    if (req.getIssuer() != null && req.getNetwork() != null) { 
        return ccRepository.findByIssuerAndCreditNetwork(req.getIssuer(), req.getNetwork().name()); 
    }
    if (req.getIssuer() != null) { 
        return ccRepository.findByIssuer(req.getIssuer()); 
    }
    if (req.getNetwork() != null) { 
        return ccRepository.findByCreditNetwork(req.getNetwork().name()); 
    }
    return null;
}

However, I don't like this code since I will have to create a combination of all the properties and will be very messy. In the future, I plan to have 15 properties so the 'if' chain is not possible.

I would like to ask you how can I create dynamic queries using spring-data-redis, so Redis can return the intersection based on the object properties in a better way than checking each property?

Have tried using MethodHandle by hardcoding (I previously deleted from the repository findByIssuerAndCreditNetwork) a method name that would be dynamic generated like this:

MethodType methodType = MethodType.methodType(cardList.getClass(), String.class, String.class);
// Dynamic create 'findByIssuerAndCreditNetwork'
MethodHandle methodHandle = MethodHandles.lookup().findVirtual(CreditCardRepository.class, "findByIssuerAndCreditNetwork", methodType);

But seems MethodHandle does not work since I got below error:

java.lang.NoSuchMethodException: no such method: com.creditcard.dao.CreditCardRepository.findByIssuerAndCreditNetwork(String,String)ArrayList/invokeInterface

mp911de
  • 17,546
  • 2
  • 55
  • 95
Federico Piazza
  • 30,085
  • 15
  • 87
  • 123
  • Interesting question. Since you are defining the actual rules of what method to call, you would still need to traverse all the properties of `CreditCardGetReq` – Eugene Feb 28 '17 at 21:44
  • @Eugene, yes, I don't care doing the traverse of all properties that's the easy part, however doing the findByX combination is the challenge. I'll try if building a dynamic string such us "findByXAndY" dynamic and calling the repo through reflection does the trick but I feel I'm missing something here – Federico Piazza Feb 28 '17 at 21:58
  • Actually I was thinking in that direction also... but via `MethodHandle`s, not reflection, which will work if you have the actual instance of the repository. The problem is that method handles need their invoke arguments obviously – Eugene Feb 28 '17 at 22:03
  • @Eugene, have tried MethodHandle but seems it doesn't work. Have updated the question with those details too – Federico Piazza Feb 28 '17 at 22:47
  • I have not used this myself, but will `RedisCallback` and `RedisTemplate` not give you what you want? Although it will be less abstracted that using pure Spring Data – Leon Mar 01 '17 at 07:03
  • @Leon, RedisTemplate gives me what I want, however I have to build all the collection maintenance which is error prone and takes more time. Spring-data-redis already does everything for me, but the cons is that its repositories aren't dynamic. – Federico Piazza Mar 01 '17 at 14:52

1 Answers1

4

Right now, there's no support to create dynamic queries. It sounds a bit as if Query by Example could be the thing you're looking for. Spring Data MongoDB and Spring Data JPA already implement Query by Example.

A query is created by the data store module to match an example domain object:

Person person = new Person();                         
person.setFirstname("Dave");                          

Example<Person> example = Example.of(person); 

MongoRepository repo = …
List<Person> result = repo.findAll(example); // returns all objects that with Dave in firstname

Query by Example is not supported by Spring Data Redis right now but it should be possible to provide basic support.

I created a ticket DATAREDIS-605 to track the progress of this feature.

mp911de
  • 17,546
  • 2
  • 55
  • 95
  • Thanks Mark, this feature would definitely be great to have. I added myself as a watcher to Jira. – Federico Piazza Mar 01 '17 at 15:42
  • Mark, When this feature is released, if you want to provide the spring-data-redis version and the specific sample I could mark you this answer as accepted for the community to know how to use it. – Federico Piazza Mar 03 '17 at 18:03
  • Mark, I noticed in the Jira ticket that you have completed the development of the query by example. Is this already available? The code you provided is the same to use it with spring data redis? If so please let me know to accept the answer, if not can you provide a sample code? Thanks a lot – Federico Piazza Jan 10 '18 at 17:31
  • It’s available in the snapshot build of version 2.1. Give it a try, always happy to receive early feedback. – mp911de Jan 10 '18 at 18:47
  • 1
    can you update this answer according to the feature released? – Federico Piazza Jul 26 '18 at 18:32
  • @mp911de - Could you please guide me on https://stackoverflow.com/questions/53134556/caused-by-java-lang-illegalargumentexception-containing-1-iscontaining-co ? – Jeff Cook Nov 04 '18 at 06:52