5
votes

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

1
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
@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
Actually I was thinking in that direction also... but via MethodHandles, 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
@Eugene, have tried MethodHandle but seems it doesn't work. Have updated the question with those details too - Federico Piazza
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

1 Answers

4
votes

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.