Returning only the first 10 record - Redis OM - spring-boot

I’m using Redis OM for spring boot, I am having trouble querying objects because it only returns the first 10 records.
Repository Class:
public interface RedisBillerRepository extends RedisDocumentRepository<Biller, Long> {
List<Biller> findByClientIds(String clientId);
}
Is there a way to return ALL the objects with the specific clientId? Not the first 10 only.

The only way which i found was with the interface Page. For example your Repository would look like this:
public interface RedisBillerRepository extends RedisDocumentRepository<Biller, Long> {
Page<Biller> findByClientIds(String clientId, Pageable pageable);
}
And your class could look like this
public class BillerService {
#Autowired
RedisBillerRepository redisBillerRepository;
public List<Biller> getAllClientsById(String clientId){
Pageable pageRequest = PageRequest.of(0, 500000);
Page<Biller> foundBillers = redisBillerRepository.findByClientIds(clientId, pageRequest);
List<Biller> billersAsList = foundBillers.getContent();
return billersAsList;
}
}
You have to set the limit for now.

I'm the author of the library... #member2 is correct. RediSearch currently has a default for the underlying FT.SEARCH (https://redis.io/commands/ft.search/) method of returning the first 10 records found. To override that, the only way to do so currently is to use the Pagination constructs in Spring.
I will expose a configuration parameter in upcoming versions to set the MAX globally.

Related

Spring Data - Build where clause at runtime

In Spring Data, how can I append more conditions to an existing query?
For example, I have the CrudRepository below:
#RepositoryRestResource
public interface MyRep extends CrudRepository<MyObject, Long> {
#Query("from MyObject mo where mo.attrib1 = :attrib1")
List<MyObj> findMyObjects(String attrib1, String conditions);
}
At runtime, I will need to call "findMyObjects" with two params. The first param is obviously the value of attrib1. the second param will be a where clause that would be determined at runtime, for example "attrib2 like '%xx%' and attrib3 between 'that' and 'this' and ...". I know this extra where condition will be valid, but I don't know what attributes and conditions will be in it. Is there anyway to append this where clause to the query defined in the #Query annotation?
Unfortunately, no. There is no straightforward way to achieve that.
You'll want to use custom reporistory methods where you'll be able to inject an EntityManager and interact with EntityManager.createQuery(...) directly.
Alternatively, you can build dynamic queries using Specifications or QueryDsl.
I ended up injecting an EntityManager that I obtained in the rest controller. Posting what I did here for criticism:
The repository code:
#RepositoryRestResource
public interface MyRepo extends CrudRepository<MyObject, Long> {
default List<MyObject> findByRuntimeConditions(EntityManager em, String runtimeConditions) {
String mySql = "<built my sql here. Watch for sql injection.>";
List<MyObject> list = em.createQuery(mySql).getResultList();
return list
}
}
The Rest controller code:
#RestController
public class DataController {
#Autowired
EntityManager em;
// of course watch for sql injection
#RequestMapping("myobjects/{runtimeConditions}")
public List<MyObject> getMyObjects(#PathVariable String runtimeConditions) {
List<MyObject> list = MyRepo.findByRuntimeConditions(em, runtimeConditions);
return list;
}
}

Spring&Hibernate paggination

i want to make pagination for my site (current just practice)
I using Spring and Hibernate. I understand that i can use Critera to retrive example 5 by 5 Person name. But how to send that to JSP page and make pagination.
You can use Spring Data for this:
interface BookRepository extends JpaRepository<Book, Integer> {
}
#RestController
class BookController {
#Autowired
BookRepository bookRepository;
#GetMapping("/books")
public Page<Book> getAllBooks(Pageable pageable){
return bookRepository.findAll(pageable);
}
}
By adding Pageable to controller method Spring adds three request parameters: page, size and sort. And request will look like: curl 'http://localhost:8080/books?page=0&size=20&sort=title%2Cdesc'
Spring automatically will create that Pageable object.
Spring Data can work with this object and returns Page<T> in response, which contains among requested entities (in content array) a lot of pagination information such as totalPages, numberOfElements.

Why is this method in a Spring Data repository considered a query method?

We have implemented an application that should be able to use either JPA, Couchbase or MongoDB. (for now, may increase in the future). We successfully implemented JPA and Couchbase by separating repositories for each e.g. JPA will come from org.company.repository.jpa while couchbase will come from org.company.repository.cb. All repository interfaces extends a common repository found in org.company.repository. We are now targeting MongoDB by creating a new package org.company.repository.mongo. However we are encountering this error:
No property updateLastUsedDate found for type TokenHistory!
Here are our codes:
#Document
public class TokenHistory extends BaseEntity {
private String subject;
private Date lastUpdate;
// Getters and setters here...
}
Under org.company.repository.TokenHistoryRepository.java
#NoRepositoryBean
public interface TokenHistoryRepository<ID extends Serializable> extends TokenHistoryRepositoryCustom, BaseEntityRepository<TokenHistory, ID> {
// No problem here. Handled by Spring Data
TokenHistory findBySubject(#Param("subject") String subject);
}
// The custom method
interface TokenHistoryRepositoryCustom {
void updateLastUsedDate(#Param("subject") String subject);
}
Under org.company.repository.mongo.TokenHistoryMongoRepository.java
#RepositoryRestResource(path = "/token-history")
public interface TokenHistoryMongoRepository extends TokenHistoryRepository<String> {
TokenHistory findBySubject(#Param("subject") String subject);
}
class TokenHistoryMongoRepositoryCustomImpl {
public void updateLastUsedDate(String subject) {
//TODO implement this
}
}
And for Mongo Configuration
#Configuration
#Profile("mongo")
#EnableMongoRepositories(basePackages = {
"org.company.repository.mongo"
}, repositoryImplementationPostfix = "CustomImpl",
repositoryBaseClass = BaseEntityRepositoryMongoImpl.class
)
public class MongoConfig {
}
Setup is the same for both JPA and Couchbase but we didn't encountered that error. It was able to use the inner class with "CustomImpl" prefix, which should be the case base on the documentations.
Is there a problem in my setup or configuration for MongoDB?
Your TokenHistoryMongoRepositoryCustomImpl doesn't actually implement the TokenHistoryRepositoryCustom interface, which means that there's no way for us to find out that updateLastUsedDate(…) in the class found is considered to be an implementation of the interface method. Hence, it's considered a query method and then triggers the query derivation.
I highly doubt that this works for the other stores as claimed as the code inspecting query methods is shared in DefaultRepositoryInformation.

Dynamic Index with SpringData ElasticSearch

How can I parameterize a SpringData ElasticSearch index at runtime?
For example, the data model:
#Document(indexName = "myIndex")
public class Asset {
#Id
public String id;
// ...
}
and the repository:
public interface AssetRepository extends ElasticsearchCrudRepository<Asset, String> {
Asset getAssetById(String assetId);
}
I know I can replace myIndex with a parameter, but that parameter will be resolved during instantiation / boot. We have the same Asset structure for multiple clients / tenants, which have their own index. What I need is something like this:
public interface AssetRepository extends ElasticsearchCrudRepository<Asset, String> {
Asset getAssetByIdFromIndex(String assetId, String index);
}
or this
repoInstance.forIndex("myOtherIndex").getAssetById("123");
I know this does not work out of the box, but is there any way to programmatically 'hack' it?
Even though the bean is init at boot time, you can still achieve it by spring expression language:
#Bean
Name name() {
return new Name();
}
#Document(indexName="#{name.name()}")
public class Asset{}
You can change the bean's property to change the index you want to save/search:
assetRepo.save(new Asset(...));
name.setName("newName");
assetRepo.save(new Asset(...));
What should be noticed is not to share this bean in multiple thread, which may mess up your index.
Here is a working example.
org.springframework.data.elasticsearch.repository.ElasticSearchRepository has a method
FacetedPage<T> search(SearchQuery searchQuery);
where SearchQuery can take multiple indices to be used for searching.
I hope it answers

Return custom-typed object from JpaRepository

I have the following repository:
public interface UserRepository extends BaseDAO<User> {
Collection<User> findByEmail(#Param("email") String email);
#Query("select new com.data.CustomUser(upper(substring(u.lastName, 1, 1)) as initial, count(*)) from User u join u.chats c where c.business=:business group by upper(substring(u.lastName, 1, 1)) order by initial")
List<CustomUser> getContactsIndex(#Param("email") String email);
}
which is exposed with Spring Data REST. The User object is a managed entity, while CustomUser not and as you can see, it's build on-fly by using custom query.
Once I want to call that function, it fails with Persistent entity must not be a null! exception. Is there any way to implement this behavior?
P.S. Expose CustomUser with separate repository is impossible because it is not a managed entity.
One challenge with using Spring Data Rest is when you hit an edge case and you don't know whether you've hit a bug or whether you're just outside the scope of what the library is intended for. In this case I think you are at the edge of what SDR will easily do for you, and it's time to implement your own controller.
Spring Data Rest is looking for an Entity - in your case a User - as the return type for ALL methods in the repository to expose under /entities/search, and breaks when it doesn't find that entity type. The User it wants to serialize isn't there, hence the "Persistent entity must not be null".
The way around this is to write a simple #Controller that has a #RequestMapping for the exact same url exposed by the repository method. This will override the SDR generated implementation for that url, and from that you can return whatever you want.
Your implementation might look something like this:
#Controller
public class CustomUserController {
private final UserRepository repository;
#Inject
public CustomUserController(UserRepository repo) {
repository = repo;
}
#RequestMapping(value = "/users/search/getContactsIndex", method = GET, produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody List<CustomUser> getContactsIndex(#RequestParam String email) {
return repository.getContactsIndex(email);
}
}
Be aware that there is a "recommended" way to override functionality this way. There is an open issue to document the best way to do this.

Resources