I am developing Spring Boot app to connect to Cassandra using Spring Data for Cassandra v1.5.10.Release.
I cannot find any reference that shows how to retrieve max(col) from Cassandra table using CassandraOperations and QueryBuilder. I tried below but it returns -
Undefined column name "max(id)"
public class CassandraTest{
#Autowired
private CassandraOperations cassandraTemplate;
public void execute() {
Select sel = QueryBuilder.select("max(id)").from("table").;
Integer maxId= cassandraTemplate.queryForObject(sel, Integer.class);
System.out.println("maxid ===> " + maxId);
}
}
After going through reference doc, I found out another way of doing it -old fashioned way without using QueryBuilder.
String selectQuery = "select max(id) as maxId from table";
Integer maxId= csOps.selectOne(selectQuery, Integer.class);
System.out.println("maxid ===> " + maxId);
I still want to know if it is possible with QueryBuilder. Interestingly, datastax driver Select api supports max function.
public Select.SelectionOrAlias max(Object column)
Description copied from class: Select.Selection
Creates a max(x) built-in function call.
Overrides:
max in class Select.Selection
Returns:
the function call.
This should work (although not tested):
Select sel = QueryBuilder.select().max(column("id")).from("table").;
Related
I am using Redis to store some data and later query it and update it with latest information.
Considering an example:
I receive File data, which carries info on the file and the physical storage location of that file.
One shelf has multiple racks, and each rack can have multiple files.
Each file has a version field, and it gets updated (incremented) when an operation on file is performed.
How do I plan to store?
I need to query based on "shelfID + rack ID" -- To get all files.
I need to query based on "shelfID + rack ID + version > XX" -- To get all files with version more than specified.
Now, to get all files belonging to a shelf and rack, is achievable in Spring Data Redis.
I create a key of the combination of 2 ID's and later query based on this Key.
private <T> void save(String id, T entity) {
redisTemplate.opsForValue().set(id, entity);
}
But, how do I query for version field?
I had kept "version" field as #Indexed, but spring repository query does not work.
#RedisHash("shelves")
public class ShelfEntity {
#Indexed
#Id
private String id;
#Indexed
private String shelfId;
#Indexed
private String rackId;
#Indexed
private String fileId;
#Indexed
private Integer version;
private String fileName;
// and other updatable fields
}
Repository method:
List<ShefEntity> findAllByShelfIdAndRackIdAndVersionGreaterThan(String centerCd,
String floorCd, int version);
Above, gives error:
java.lang.IllegalArgumentException: GREATER_THAN (1): [IsGreaterThan,
GreaterThan]is not supported for redis query derivation
Q. How do I query based on Version Greater than?
Q. Is it even possible with Spring Data Redis?
Q. If possible, how should I model the data (into what data structure), in order to make such queries?
Q. If we don't use Spring, how to do this in Redis using redis-cli and data structure?
May be something like:
<key, key, value>
<shelfId+rackId, version, fileData>
I am not sure how to model this in Redis?
Update 2:
One shelf can have N racks.
One rack can have N files.
Each file object will have a version.
This version gets updated (o -> 1 -> 2....)
I want to store only the latest version of a file.
So, if we have 1 file object
shelfId - 1
rackId - 1
fileId - 1
version - 0
.... on update of version ... we should still have 1 file object.
version - 1
I tried keeping key as a MD5 hash of shelfId + rackId, in hash data structure.
But cannot query on version.
I also tried using a ZSet.
Saving it like this:
private void saveSet(List<ShelfEntity> shelfInfo) {
for (ShelfEntity item : shelfInfo) {
redisTemplate.opsForZSet()
.add(item.getId(), item, item.getVersion());
}
}
So, version becomes the score.
But the problem is we cannot update items of set.
So for one fileId, there are multiple version.
When I query, I get duplicates.
Get code:
Set<ShelfEntity> objects = (Set<ShelfEntity>) (Object) redisTemplate.opsForZSet()
.rangeByScore(generateMd5Hash("-", shelfId, rackId), startVersion,
Double.MAX_VALUE);
Now, this is an attempt to mimic version > XX
Create ZSET for each shelfId and rackId combination
Use two methods to save and update records in Redis
// this methods stores all shelf info in db
public void save(List<ShelfEntity> shelfInfo) {
for (ShelfEntity item : shelfInfo) {
redisTemplate.opsForZSet()
.add(item.getId(), clonedItem, item.getVersion());
}
}
Use update to remove old and insert new one, Redis does not support key update as it's a table so you need to remove the existing and add a new record
public void update(List<ShelfEntity> oldRecords, List<ShelfEntity> newRecords) {
if (oldRecords.size() != newRecords.size()){
throw new IlleagalArgumentException("old and new records must have same number of entries");
}
for (int i=0;i<oldRecords.size();i++) {
ShelfEntity oldItem = oldRecords.get(i);
ShelfEntity newItem = newRecords.get(i);
redisTemplate.opsForZSet().remove(oldItem.getId(), oldItem);
redisTemplate.opsForZSet()
.add(newItem.getId(), newItem, newItem.getVersion());
}
}
Read items from ZSET with score.
List<ShefEntity> findAllByShelfIdAndRackIdAndVersionGreaterThan(String shelfId,
String rackId, int version){
Set<TypedTuple<ShelfEntity>> objects = (Set<TypedTuple<ShelfEntity>>) redisTemplate.opsForZSet()
.rangeWithScores(generateMd5Hash("-", shelfId, rackId), new Double(version),
Double.MAX_VALUE);
List<ShelfEntity> shelfEntities = new ArrayList<>();
for (TypedTuple<ShelfEntity> entry: objects) {
shelfEntities.add(entry.getValue().setVersion( entry.getScore().intValue()));
}
return shelfEntities;
}
I was thinking about the architecture to implement Quartz scheduler in my application to schedule jobs. I have angular table where i am showing all jobs from database table QUARTZ_JOB_DETAILS. But No pagination support found in API. I searched in StdJDBCDelegate.java class too.
I am using spring boot 2.1.7 and Quartz 2.3.1
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
Can anyone suggest how can i achieve pagination on Quartz tables.
You are Correct Anurag, no API support available. Alternatively i created a hibernate entity pojo class which contains id, jobName and other relevant private variables & getters/setters. Then passed this pojo to one of my JPARepository. Now there would be a database table lets say custom_job_detais. This way there was duplicacy of data stored in database but it was workaround actually.
Jobname column stored into both the tables would remain same. For frontend pagination purpose we can use custom_job_detais and for job management QUARTZ_JOB_DETAILS table will be used.
Save data into custom_job_detais table before saving into QUARTZ_JOB_DETAILS. Now easily we can implement pagination support on custom_job_detais table rather than QUARTZ_JOB_DETAILS.
-Happy coding
I don't think Quartz provides any API for pagination.
If anyone wants a page response for quartz jobs or triggers, either they can create pojo and retrieve the data directly from the whatever the JobStore DB
or
can use spring Pageable and/or PagedListHolder implementation to create PageImpl from List and return.
My Code example to get all group jobs is as follows,
public SchedulerResponse findAllJobs(Integer pageNumber, Integer pageSize) {
Pageable pageable = PageRequest.of(pageNumber, pageSize);
SchedulerResponse responseBean = new SchedulerResponse();
List<JobDetailRequestBean> jobDetailRequestBeanList= new ArrayList<>();
PagedListHolder<JobDetailRequestBean> jobDetailRequestBeanPLH = new PagedListHolder<>();
try {
for (String groupName : scheduler.getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
String jobName = jobKey.getName();
String jobGroup = jobKey.getGroup();
//get job's trigger
List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
JobDetail jobDetail = scheduler.getJobDetail(jobKey(jobName, jobGroup));
if(Objects.nonNull(jobDetail)) {
JobDetailRequestBean jobDetailRequestBean =JobDetailRequestBean.buildJobDetail(
jobDetail, triggers);
if(Objects.nonNull(jobDetailRequestBean))
jobDetailRequestBeanList.add(jobDetailRequestBean);
}
}
}
jobDetailRequestBeanPLH.setSource(jobDetailRequestBeanList);
jobDetailRequestBeanPLH.setPageSize(pageSize);
jobDetailRequestBeanPLH.setPage(pageNumber);
responseBean.setResult(new PageImpl<>(jobDetailRequestBeanPLH.getPageList(), pageable, jobDetailRequestBeanList.size()));
responseBean.setResultCode(HttpStatus.OK);
} catch (SchedulerException e) {
LOG.error(e.getLocalizedMessage());
e.printStackTrace();
responseBean.setResultCode(HttpStatus.INTERNAL_SERVER_ERROR);
responseBean.setResult(e.getLocalizedMessage());
}
return responseBean;
}
So, I've already done this using the standard Spring Data JPA interface which extends PagingAndSortingRepository in order to achieve pagination and sorting for a REST API. The thing is, now I want to achieve the very same thing but now using just vanilla JPA and so far so good I managed to get my API to paginate but the sorting doesn't work at all. Every time I try to set the parameter (from a pageable object using pageable.getSort()) it ends with a query error (either if I just send a string as parameter like "name" or just send the sort object, it shows errors).
Here's some code:
My repo implementation:
#Override
public List<Project> findByAll(Pageable pageable) {
Query query = em.createQuery("SELECT project FROM Project project ORDER BY :sort");
query.setParameter("sort", pageable.getSort());
query.setMaxResults(pageable.getPageSize());
query.setFirstResult(pageable.getPageSize() * pageable.getPageNumber());
return query.getResultList();
}
My service:
#Override
public Page<Project> findAll(Pageable pageable) {
objects = Lists.newArrayList(repository.findByAll(pageable));
PageImpl<Project> pages= new PageImpl<Project>(objects, pageable, repository.count());
return pages;
}
To be clear, I'm filling the Pageable object via URI and from the console I can say it's actually getting the data, so I assume the problem is with the repo.
Edit: This is the error I get when I replace the setParameter("sort", ...) for a hardcoded string aka query.setParameter("sort", "name"):
java.lang.NumberFormatException: For input string: "name"
And I think this method should stand for strings as well. If I use query.setParameter("sort", pageable.getSort()), the error is the same.
The order by cannot be set as a query parameter. Also, the Pageable.getSort().toString() likely won't return a string suitable for use in an order by clause as it will result in a String that represents the Order as property: ORDER, note the colon.
Here are some modifications that will work, assuming Java 8...
String order = StringUtils.collectionToCommaDelimitedString(
StreamSupport.stream(sort.spliterator(), false)
.map(o -> o.getProperty() + " " + o.getDirection())
.collect(Collectors.toList()));
Query query = em.createQuery(
String.format("SELECT project FROM Project project ORDER BY %s", order));
I'm developing a Spring web application whose persistence layer consists in Spring Roo generated JPA entities, with Hibernate as persistence provider and MySql as underlying DB.
Among my entities I have a class Detection with a tstamp java.util.Date field generated in Roo as follows:
entity jpa --class ~.data.Detection
...
field date --fieldName tstamp --type java.util.Date
...
finder add findDetectionsByTstampBetween
(the finder method was of course chosen after executing finder list)
In my controller code, at a point I invoke:
List<Detection> detections = Detection.findDetectionsByTstampBetween(from, to).getResultList();
Where from and to are two valid java.util.Date(s). When testing sample data though (after ensuring that for a given choice of from, to the returned list shouldn't be empty), I got an empty list and investigated the reasons.
I found in tomcat logs that Hibernate was generating the following SQL:
Hibernate: select detection0_.id as id1_3_, ...etc..., detection0_.tstamp as tstamp4_3_ from detection detection0_ where detection0_.tstamp>=?
I would expect the where clause should contain a trailing "AND detection0_.tstamp<=?", checking the other date range limit. I took a look at the generated Detection.findDetectionsByTstampBetween(Date minTstamp, Date maxTstamp) method in Detection_Roo_Finder.aj and actually the "AND" is present in the invocation to createQuery.
public static TypedQuery<Detection> Detection.findDetectionsByTstampBetween(Date minTstamp, Date maxTstamp) {
if (minTstamp == null) throw new IllegalArgumentException("The minTstamp argument is required");
if (maxTstamp == null) throw new IllegalArgumentException("The maxTstamp argument is required");
EntityManager em = Detection.entityManager();
TypedQuery<Detection> q = em.createQuery("SELECT o FROM Detection AS o WHERE o.tstamp BETWEEN :minTstamp AND :maxTstamp", Detection.class);
q.setParameter("minTstamp", minTstamp);
q.setParameter("maxTstamp", maxTstamp);
return q;
}
Any idea what could cause the problem?
I've finally found the solution to the riddle and, as it turned out, the issue had nothing to do with JPA.
The problem was that the call to the persistence layer was inserted inside a Rest service controller with the following mapping:
#ResponseBody
#RequestMapping(value="/detections", method=RequestMethod.GET, params="from, to" )
public Object getDetectionsInRange(
#RequestParam(required=true) #DateTimeFormat(pattern="yyyy-MM-dd HH:mm") final Date from,
#RequestParam(required=true) #DateTimeFormat(pattern="yyyy-MM-dd HH:mm") final Date to
)
{
...
List<Detection> detections = Detection.findDetectionsByTstampBetween(from, to).getResultList();
...
}
The error was in the definition of the params= argument in #RequestMapping, the correct format being as follows:
#RequestMapping(value="/detections", method=RequestMethod.GET, params={"from", "to"} )
This error caused another version of the controller method for /detections. In this second version I called a different finder method, which appeared to generate the wrong SQL in Hibernate.
#ResponseBody
#RequestMapping(value="/detections", method=RequestMethod.GET )
public Object getDetections(
#RequestParam(required=false, defaultValue="0") int days,
#RequestParam(required=false, defaultValue="0") int hours,
#RequestParam(required=false, defaultValue="0") int minutes
)
{
...
List<Detection> detections = Detection.findDetectionsByTstampGreaterThanEquals( ... ).getResultList();
...
}
I want to set a parameter in a named query (JPA 2.0), so my dataTable would render the respective dataSet. The parameter is obtained remotely and injected in a AbstractFacade class.
I've tried to achieve this through the code above, but it's not working.
Can someone help me?
AbstractFacade (main code):
private String prefDep;
public List<T> findByPrefDep() {
prefDep= FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("xPrefDep");
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).setParameter("prefDep", prefDep).getResultList();
}
The Entity class (main code):
#NamedQuery(name = "Capacitacao.findByPrefDep", query = "SELECT c FROM Capacitacao c WHERE c.prefDep = :prefDep"),
The AbstractController:
public Collection<T> getItems() {
if (items == null) {
items = this.ejbFacade.findByPrefDep();
}
return items;
}
There is no exception launched, but the dataSet rendered corresponds to a findAll named query.
Thanks in advance.
Your code doesn't use your named query at all. A named query has a name, and your code doesn't use that name anywhere.
Use
getEntityManager().createNamedQuery("Capacitacao.findByPrefDep", Capacitacao.class)
.setParameter("prefDep", prefDep)
.getResultList();
You could have found that yourself by simply reading the EntityManager javadoc.