Spring Data Cassandra Pagination - spring-boot

Can anybody know how to achieve the pagination in Spring Data Cassandra ?
I have tried all the possible alternatives to implement pagination for my table.
One of the stackoverflow answers says it is not provided directly.
Paging SELECT query results from Cassandra in Spring Boot application
As per documentation (https://docs.spring.io/spring-data/cassandra/docs/current-SNAPSHOT/reference/html/#cassandra.repositories.queries) says CrudRepository provides method with Pageable.
org.springframework.data.repository.CrudRepository does not provide.
I am using Cassandra 3.11.3 and Spring Boot 1.5.1.RELEASE.
Can one provide me simple pagination demo with Spring Data Cassandra ?

The pagination in Spring Data Cassandra work in 'forward only' basis like the way iterable interface works.
#Autowired
PersonRepository personrepo;
#GetMapping("/all/{page}")
public List<Person> getPaginated(#PathVariable Integer page) {
int currpage = 0, size = 2;
Slice<Person> slice = personrepo.findAll(CassandraPageRequest.first(size));
while(slice.hasNext() && currpage < page) {
slice = personrepo.findAll(slice.nextPageable());
currpage++;
}
return slice.getContent();
}
Where your repository contains:
public interface PersonRepo extends CrudRepository<Person, Integer> {
Slice<Person> findAll(Pageable pr);
}
Hence, you keep querying the result until the desired location of the data is reached.

This is the simplest way to do pagination in reactive Cassandra or regular Cassandra with spring data
In your repository class create custom methods with Query annotation (key point here is for second method there should be a offset that needs to sent as a parameter) Assuming date created is a clustering key
user table primary key(user_id, date_created)
Ex:-
#Query("select * from user order by date_created asc Limit :count)
Flux<UserEntity> findFirstSetOfUsers(#Param(count) int noOfRecords);
Next set of records
#Query("select * from user where date_created > :dateCreated order by date_created asc Limit :count)
Flux<UserEntity> findNextSetOfUsers(#Param(count) int noOfRecords, #Param(dateCreated) Date dateCreated);

Related

How to pass pageable for native query?

I was doing a project and there i had a requirement of using pageable object and recieved page object from JPA.
Does anyone have any idea on how to use this?
#Repository
public interface CustomerRepository extends JpaRepository<Customer,Long>{
#Query("SELECT * FROM WHERE name=?1 AND surname=?2 ", nativeQuery = true)
List<Customer> findAllByNameAndSurname(String name,String surname);
}
I want a page List for result fetch from this query.
Spring Data JPA and native queries with pagination is not supported in Spring. According to documentation , Spring Data JPA repositories can be used with native queries and pagination. But, in real life Spring Data JPA (1.10.2.RELEASE) requires some additional fix.
You have to use this if you want pagination support.
List<Customer> customers = customerRepository.findAllByNameAndSurname(name,username);
PagedListHolder<Customer> pages = new PagedListHolder(customers);
pages.setPage(currentPageNumber); //set current page number
pages.setPageSize(pageSize); // set the size of page
pages.getPageList(); // return the list of items(or in your case List<Customer> ) of current page
Try this:
#Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Page<Customer> findAllByNameEqualsAndSurnameEquals(String name, String surname, Pageable pageable);
}
I am pretty sure JpaRepository can handle your Pageable parameter.
Also, method name has to be as I mentioned as spring creates queries based on method name (uses reflection).
If you really need to execute NATIVE QUERY you will have to find other solution but I do not recommend the one provided by Dasari Swaroop Kumar as it just queries all objects from database and then kinda filters it in memory.
Also to that native query solution - you can extend your method definition to additional 2 parameters for page and pageSize and append them to your native query and leave repository to return plain List and then create PageImpl object in the layer that calls your CustomerRepository object.

Tenant Id filter where Multi-tenancy in single database

I'm using JDBCTemplate and not using Hibernate and running native SQL queries.
I need to attach/append tenant id to any query which is being executed.
For multiple database i come across this - https://github.com/openMF/mifosx-admin/blob/master/src/main/java/org/mifosx/admin/domain/BaseDAO.java
Can someone help me with suggestion or comments how to attach the tenant id dynamically as jdbc interceptor or apply filter for queries?
Currently all queries goes like select * from...where tenant id = test
Thanks.
Store the tenant id as soon as you can determine it's value, in a thread-local, perhaps facilitated by a servlet filter.
If all such entities implement an interface which exposes a 'tenantId' property, Then update this property in your entity objects, from the above thread-local tenantId, in your BaseDAO class.
By way of example, you could create a singleton which keeps a threadlocal copy of your tenant id, assuming that it is an Integer. Here's one way to do this.
public enum ThreadState {
INSTANCE
;
private ThreadLocal<Integer> tenantId = new ThreadLocal<>();
public void setTenantId(Integer tid) {
tenantId.set(tid);
}
public Integer getTenantId() {
return tenantId.get();
}
}
Then, in that place in your code where you determine what the tenant ID is for the given request, stash it into our new threadlocal as follows:
ThreadState.INSTANCE.setTenantId(tenantId);
And finally, in your DAO class where you are formulating a query and need to access the tenant ID write the following:
Integer tenantId = ThreadState.INSTANCE.getTenantId()
At this point you can use the tenantId when formulating your query, or update a new entity object prior to storing it.

Spring JPA Repository Sub-Query In Native Query

I am trying to run a native query on a repository method so that it returns the results with some counts. It was too complicated to do with JPQL, so I opted for a native query instead.
Repository
#RepositoryRestResource(collectionResourceRel="projects", path="projects")
interface ProjectRepository extends BaseRepository<Project, Long>, ProjectRepositoryCustom {
#Query(
value="SELECT p.id, p.user_id, p.title, p.description, p.created_on, p.version,(SELECT COUNT(0) FROM projectparts WHERE project_id = p.id) AS parts,(SELECT COUNT(0) FROM requests WHERE project_id = p.id) AS requests FROM projects AS p ORDER BY ?#{#pageable}",
countQuery="SELECT COUNT(0) FROM projects",
nativeQuery=true
)
Page<Project> findAll(Pageable pageable)
}
The entity has 2 properties annotated with #Transient so that the info is not persisted to the database. All the data comes back fine except the 2 transient properties which return null for the values. When I copy the query from the console and paste it in MySQL Workbench, the results are as expected and I see the counts that I need. Anyhow, not sure if there is anything else that needs to be done in order to get this native query to work as an annotation. I hard coded a value in the sub-query SELECT 55 FROM... just to see if it was a problem with the count and it still returned as null. I ran the query in Workbench and it works fine.
I've tried changing the transient property type from Integer, Long, BigInteger, long, int... and none of that made a difference. Since I'm using Groovy, I also tried def to let Groovy infer the type and that didn't work either.
I also tried running the project from the terminal instead and it still didn't work. I've tried it on a Mac and Linux and had no luck with displaying the results of the counts.
This will not work. You could use an SQLConstructorExpression however the returned instances would be unmanaged which is a major drawback.
An better option is to create a simple DB view which holds the pieces of summary info for the Project. You can them map the Project entity to both it's table and the associated summary view using the #SecondaryTable functionality of JPA.
https://en.wikibooks.org/wiki/Java_Persistence/Tables#Example_mapping_annotations_for_an_entity_with_multiple_tables
An added benefit is that you can sort and query on the summary values as for any other property.
Updated mapping:
#Entity
#Table(name = "projects")
#SecondaryTable(name = "projects_summary_vw")
public class Project{
//use Integer rather than int to avoid issue outlined here:
//http://stackoverflow.com/a/37160701/1356423
#Column(name = "parts", table = "projects_summary_vw",
insertable="false", updateable="false")
private Integer partsCount;
#Column(name = "requests", table = "requestsCount"
insertable="false", updateable="false")
private Integer requestsCount;
//other mappings as required
}
No Custom query required:
#RepositoryRestResource(collectionResourceRel="projects",
path="projects")
interface ProjectRepository extends BaseRepository<Project, Long>,
ProjectRepositoryCustom {
}
An alternative non-JPA compliant solution may be to use some vendor specific extension rather than a view. Hibernate for example has an #Formula annotation which could be used:
https://docs.jboss.org/hibernate/orm/5.1/javadocs/org/hibernate/annotations/Formula.html
#Entity
#Table(name = "projects")
public class Project{
#Formula("my count query as native sql")
private Integer partsCount;
#Formula("my count query as native sql")
private Integer requestsCount;
//other mappings as required
}

How do I migrate my JPA DAO to Spring Data with second level cache?

I have bunch of JPA DAOs im looking to migrate to Spring Data JPA. Some of my DAOS have second-level / query caching set up.
I have a process where I only retrieve the ID in my queries, and then look up the entity using findByID(). This way, only the id's are multiplied in the different query caches, and the entire entities are in the second level cache.
Example:
#NamedQuery(name = "SystemUser.findByEmail",
query = "SELECT u.id FROM SystemUser u WHERE email=:email"),
…
public SystemUser findByEmail(String email) {
TypedQuery<Long> q = getEntityManager().createNamedQuery("SystemUser.findByEmail", Long.class);
q.setParameter("email", email);
q.setHint("org.hibernate.cacheable", true);
q.setHint("org.hibernate.cacheRegion", "query.systemUser");
List<Long> res = q.getResultList();
if (res != null && res.size() > 0) {
return findById(res.get(0));
}
return null;
}
I have several more findBy…-methods, all doing it like this. It feels like a good way to keep cache memory consumption down.
I'm kind of new to the Spring Data JPA business, but I can't see how I would go about realizing this here? The #Cacheable annotations seems only to deal with query caches, which to me would duplicate the entities in each query cache?
Is there any way to do this with Spring Data? Pointers would be much appreciated.
In Spring Data JPA just create a findByEmail method and either Spring Data JPA will found your named query or create one itself.
public class SystemUserRepository extends CrudRepository<SystemUser, Long> {
SystemUser findByEmail(String email);
}
Should be all you need to get the query executed and the desired result. Now with the #QueryHints you can add the hints you are setting now.
public class SystemUserRepository extends CrudRepository<SystemUser, Long> {
#QueryHints(
#QueryHint(name="org.hibernate.cacheable", value="true"),
#QueryHint(name="org.hibernate.cacheRegion", value="query.systemUser") )
SystemUser findByEmail(String email);
}
The result will be cached and still the user will come from the 2nd level cache (if available, else created). Assuming of course your entity is #Cacheable.
A nice read on how the 2 different caches work (together) can be found here. A small snippet on how the query cache works.
The query cache looks conceptually like an hash map where the key is composed by the query text and the parameter values, and the value is a list of entity Id's that match the query:
If you want more complex logic (and really implement the optimization you did) you can always implement your own repository.

Spring Data: "delete by" is supported?

I am using Spring JPA for database access. I am able to find examples such as findByName and countByName, for which I dont have to write any method implementation. I am hoping to find examples for delete a group of records based on some condition.
Does Spring JPA support deleteByName-like delete? Any pointer is appreciated.
Regards and thanks.
Deprecated answer (Spring Data JPA <=1.6.x):
#Modifying annotation to the rescue. You will need to provide your custom SQL behaviour though.
public interface UserRepository extends JpaRepository<User, Long> {
#Modifying
#Query("delete from User u where u.firstName = ?1")
void deleteUsersByFirstName(String firstName);
}
Update:
In modern versions of Spring Data JPA (>=1.7.x) query derivation for delete, remove and count operations is accessible.
public interface UserRepository extends CrudRepository<User, Long> {
Long countByFirstName(String firstName);
Long deleteByFirstName(String firstName);
List<User> removeByFirstName(String firstName);
}
Derivation of delete queries using given method name is supported starting with version 1.6.0.RC1 of Spring Data JPA. The keywords remove and delete are supported. As return value one can choose between the number or a list of removed entities.
Long removeByLastname(String lastname);
List<User> deleteByLastname(String lastname);
2 ways:-
1st one Custom Query
#Modifying
#Query("delete from User where firstName = :firstName")
void deleteUsersByFirstName(#Param("firstName") String firstName);
2nd one JPA Query by method
List<User> deleteByLastname(String lastname);
When you go with query by method (2nd way) it will first do a get call
select * from user where last_name = :firstName
Then it will load it in a List
Then it will call delete id one by one
delete from user where id = 18
delete from user where id = 19
First fetch the list of object, then for loop to delete id one by one
But, the 1st option (custom query),
It's just a single query
It will delete wherever the value exists.
Since in 2nd option it is making multiple DB query, try to use the first option.
Go through this link too https://www.baeldung.com/spring-data-jpa-deleteby
If you take a look at the source code of Spring Data JPA, and particularly the PartTreeJpaQuery class, you will see that is tries to instantiate PartTree.
Inside that class the following regular expression
private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(find|read|get|count|query)(\\p{Lu}.*?)??By")
should indicate what is allowed and what's not.
Of course if you try to add such a method you will actually see that is does not work and you get the full stacktrace.
I should note that I was using looking at version 1.5.0.RELEASE of Spring Data JPA
If you will use pre defined delete methods as directly provided by spring JPA then below two queries will be execute by the framework.
First collect data(like id and other column) using by execute select query with delete query where clause.
then after getting resultSet of first query, second delete queries will be execute for all id(one by one)
Note : This is not optimized way for your application because many queries will be execute for single MYSQL delete query.
This is another optimized way for delete query code because only one delete query will execute by using below customized methods.
#NamedNativeQueries({
#NamedNativeQuery(name = "Abc.deleteByCreatedTimeBetween",
query = "DELETE FROM abc WHERE create_time BETWEEN ?1 AND ?2")
,
#NamedNativeQuery(name = "Abc.getByMaxId",
query = "SELECT max(id) from abc")
})
#Entity
public class Abc implements Serializable {
}
#Repository
public interface AbcRepository extends CrudRepository {
int getByMaxId();
#Transactional
#Modifying
void deleteByCreatedTimeBetween(String startDate, String endDate);
}
It works just
import org.springframework.transaction.annotation.Transactional;
#Transactional
Long removeAddressByCity(String city);
Yes , deleteBy method is supported
To use it you need to annotate method with #Transactional
here follows my 2 cents. You can also use native queries, like:
#Modifying
#Query(value="delete from rreo r where r.cod_ibge = ?1 and r.exercicio= ?2", nativeQuery = true)
void deleteByParameters(Integer codIbge, Integer exercicio);
#Query(value = "delete from addresses u where u.ADDRESS_ID LIKE %:addressId%", nativeQuery = true)
void deleteAddressByAddressId(#Param("addressId") String addressId);

Resources