0

I'm trying to inject anything other than values using the @Query N1QL query syntax but can't get it to work.

Here is the pure N1QL query:

SELECT * from `my-bucket` WHERE _class = 'my.package.MyModel' AND myParam = 'myValue'

I successfully manage to inject a value in Java and get the correct results:

// In my repository
@Query("#{#n1ql.selectEntity} WHERE myParam = $1 AND #{#n1ql.filter}")
Collection<MyModel> myCustomSearch(String value);
// In my business code
myRepository.myCustomSearch("myValue");

However I can't manage to inject anything else (like a param). This doesn't work:

// In my repository
@Query("#{#n1ql.selectEntity} WHERE $1 = 'myValue' AND #{#n1ql.filter}")
Collection<MyModel> myCustomSearch(String param);
// In my business code
myRepository.myCustomSearch("myParam");

Considering that in a pure N1QL query, the value usually is wrapped in single quotes ('') and it's not necessary when injecting it, I'm afraid spring-data-couchbase always wraps injected elements with single or double quotes (thus my query is transformed into

SELECT * from `my-bucket` WHERE _class = 'my.package.MyModel' AND 'myParam' = 'myValue'

which explains why Couchbase doesn't return any result).

Did I miss something? Otherwise, is there a way to bypass the quote injection from spring-data-couchbase?

I am aware I could simply use a com.couchbase.client.java.Bucket and call query on it, but this loses the whole point of spring-data-couchbase for me, which is to always manipulate POJOs and hide the JSON manipulation.

I appreciate any help!

4

2 回答 2

0

Edit: See @simon-baslé 's answer, even shorter and more elegant than mine.


I think I found the best solution for my needs, in the form of implementing a custom method in my repository.

public interface MyRepositoryCustom {
  Collection<MyModel> customN1qlQuery(String query); 
}

public interface MyRepository extends CrudRepository<MyModel, String>, MyRepositoryCustom { } 

public class MyRepositoryImpl implements MyRepositoryCustom { 

  @Autowired
  RepositoryOperationsMapping templateProvider; 

  @Override
  public Collection<MyModel> customN1qlQuery(String query) {
    CouchbaseOperations template = templateProvider.resolve(MyRepository.class, MyModel.class);
    return template.findByN1QL(N1qlQuery.simple(query), MyModel.class);
  }
}

// In my business code
Collection<MyModel> result = myRepository.customN1qlQuery("select META().id AS _ID, META().cas AS _CAS, * from `" + bucket.name() + "` where _class = '"
            + MyModel.class.getCanonicalName() + "' and " + myCustomParam + " = '" + myCustomValue + "'");

I keep manipulating POJOs, yet I have full control over the N1QL query statement. The only (minor) drawback is that I can no longer inject Spring SpEL syntax (like #{#n1ql.selectEntity} to retrieve the full entity, or #{#n1ql.filter} to filter on the entity class name). I found literal equivalent (as shown in my example above), so I can live with that.

于 2016-08-11T08:38:06.723 回答
0

$1 is actually just using the N1QL syntax for parameterized statements. I haven't extensively tested it, but you could use SpEL here. The way to use a method parameter in SpEL is to use the #{[x]} syntax where x is the 0-based index of the parameter to use.

Careful when mixing the two approaches though: as soon as a $x is detected in the statement, Spring Data Couchbase will use all method arguments as the array to populate $x placeholders. So the first argument will map to $1, the second to $2, etc...

So to use both syntax (one to dynamically choose a field name, the other to let N1QL inject the searched value) you'd have to write something like:

@Query("#{#n1ql.selectEntity} WHERE #{[0]} = $2 AND #{#n1ql.filter}")
public List<Entity> findAllBySomeCriteria(String fieldName, String value);

Notice how #{[0]} and $1 will both point to the fieldName parameter, so the N1QL placeholder used is $2, that points to value.

于 2016-08-11T08:42:34.047 回答