Spring Data Neo4j Map query result to Non entity POJOs - spring-data-neo4j

I am using spring-data-neo4j. I want the results of the query to be mapped to a non-entity POJO.
This is how the repository looks like
public interface CategoryRepository extends GraphRepository<Category> {
#Query("Match (:Client {email: {clientEmail}})-[:client]->()" + "-[:owns]->()-[:had]->(a:visit)-[:to]->()-[:category]->(b)"
+ " where a.lastPredictionTime > {startTime} and a.lastPredictionTime < {endTime}" + " with Distinct b.name as category, sum(a.timeSpent) as sum order by sum desc"
+ " return collect({category: category, timeSpent: sum})[{start}..{end}]")
List<CountDetailsByDate> getTopCategoriesByTimeSpent(#Param("clientEmail") String clientEmail, #Param("start") int start, #Param("end") int end,
#Param("startDate") long startDate, #Param("endDate") long endDate);
}
The CountDetailsByDate object is neither a node entity nor a relationship entity, I want the result of the query to be mapped to it. Is there any way to do that?

You should create a CountDetailsByDate class and annotate it with #QueryResult.
You can either define it as an inner class in your CategoryRepository repository interface or you can create it somewhere else.
The inner-class code will be like this :
public interface CategoryRepository extends GraphRepository<Category> {
#Query("Match (:Client {email: {clientEmail}})-[:client]->()" + "-[:owns]->()-[:had]->(a:visit)-[:to]->()-[:category]->(b)"
+ " where a.lastPredictionTime > {startTime} and a.lastPredictionTime < {endTime}" + " with Distinct b.name as category, sum(a.timeSpent) as sum order by sum desc"
+ " return collect({category: category, timeSpent: sum})[{start}..{end}]")
List<CountDetailsByDate> getTopCategoriesByTimeSpent(#Param("clientEmail") String clientEmail, #Param("start") int start, #Param("end") int end,
#Param("startDate") long startDate, #Param("endDate") long endDate);
}
#QueryResult
class CountDetailsByDate {
// class variables and ...
}
If you choose to create a separate class , make sure you add the package path to component scan of your Neo4j Config class. It will be something like this :
#Configuration
#EnableTransactionManagement
#EnableNeo4jRepositories(basePackages = {"XXX-PATH-TO-Repo-packages"})
#ComponentScan(basePackages = "YYY-PATH-TO-YOUR-CLASS-Package")
public class Neo4jConfig extends Neo4jConfiguration {
....
}
Hope this will help.

Related

How to test an Update class in apex

Hello I am new to apex and soql, I would like some help on my test class, below is the code i am having trouble with,
public class UpdateMyCard {
#AuraEnabled(cacheable=false)
public static Card__c updateCard(Double Amount){
String userId = UserInfo.getUserId();
Card__c myCard = [SELECT Id, Card_no__c, total_spend__c From Card__c Where OwnerId =:userId];
myCard.total_spend__c = myCard.total_spend__c + Amount;
try{
update myCard;
}catch (Exception e) {
System.debug('unable to update the record due to'+e.getMessage());
}
return myCard;
}
}
Test Class
#isTest
public static void updatecard(){
UpdateMyCard.updateCard(200);
}
Error:
System.QueryException: List has no rows for assignment to SObject
Your code assumes there's already a Card record for this user. And it's exactly 1 record (if you have 2 your query will fail). I'm not sure what's your business requirement. Is it guaranteed there will always be such record? Should the code try to create one on the fly if none found? You might have to make that "updateCard" bit more error-resistant. Is the total spend field marked as required? Because "null + 5 = exception"
But anyway - to fix your problem you need something like that in the test.
#isTest
public static void updatecard(){
Card__c = new Card__c(total_spend__c = 100);
insert c;
UpdateMyCard.updateCard(200);
c = [SELECT total_spend__c FROM Card__c WHERE Id = :c.Id];
System.assertEquals(300, c.total_spend__c);
}

List of instances with minimal date of their group

I'm working on a Java project, using Hibernate to administrate data on a SQL database.
I try to fetch a list of instances from the Database, that have a minimal timestamp of the group they share. The group is modeled by a container.
Here is a minimal model sketch:
#Entity
#Table(name = "object")
public class Object implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.Auto)
long obj_id;
#Column(name = "time_stamp", nullable = false)
Date timestamp;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "container_id", nullable = false)
Container con;
}
#Entity
#Table(name = "container")
public class Container{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
long con_id;
#OneToMany(mappedBy = "container")
List<object> obj_list;
}
So there are some objects with a timestamp and containers that group these objects.
For example, there are two containers, con_a and con_b:
Container con_a:
con_id = 1
obj_list = {obj_a, obj_b}
Container con_b:
con_id = 2
obj_list = {obj_c}
And three objects, obj_a, obj_b, obj_c:
Object obj_a
obj_id = 1
timestamp = 10
con = con_a
Object obj_b
obj_id = 2
timestamp = 20
con = con_a
Object obj_c
obj_id = 3
timestamp = 30
con = con_b
The desired List in this example would look like:
List<Object> = {obj_a, obj_c}
I seem to move in a circle, as I do not even know where to "start" the query:
Criteria crit = session.createCriteria(Container.class). ...
or
Criteria crit = session.createCriteria(Object.class). ...
It seems both possible for me, but i just have no idea how to go on from any of those 2 possibilities.
Update [2014.07.11, 14:19]:
I tried and started the query with the Object class and used a Subquery:
Session session = getSession();
Transaction transaction = session.beginTransaction();
DetachedCriteria IdListOfGroupMinimum = DetachedCriteria.forClass(Object.class, "obj")
IdListOfGroupMinimum.createAlias("con.id", "containerId")
.setProjection(
.Projections.projectionList()
.add(Projections.property("obj.id"))
.add(Projections.min("obj.timestamp"))
.add(Projections.groupProperty("containerId")))
.setProjection(Projection.property("obj.id"));
Criteria objects = session.createCriteria(object.class, "obj")
objects.add(Subqueries.in("obj.id", IdListOfGroupMinimum));
List<Object> = objects.list();
But I received the following error:
javax.servlet.ServletException: org.hibernate.QueryException: not an association: id
I tried to do this:
SELECT * from Object
WHERE id IN (
SELECT obj.id
FROM Object obj
INNER JOIN (
SELECT obj.containerID, MIN(obj.timestamp) AS minimum
FROM Object obj
GROUP BY obj.containerID) subquery
ON obj.containerID = subquery.containerID
WHERE obj.timestamp = subquery.minimum
)
I found a solution for my problem which is probably not the most elegant one, but it works.
Mainly I used the SQL-Query that I already posted above:
Session session = getSession();
Transaction transaction = session.beginTransaction();
//This query fetches the IDs of the smallest objects in each group with
//regard to the timestamp
Query q = session.createSQLQuery(
"SELECT obj.id FROM Object obj "
+ "INNER JOIN ( "
+ "SELECT obj.containerID, MIN(obj.timestamp) AS minimum "
+ "FROM Object obj "
+ "GROUP BY obj.containerID) subquery "
+ "ON obj.containerID = subquery.containerID "
+ "WHERE obj.timestamp = subquery.minimum "
);
//This tells Hibernate that the result are values of type Long
q.addScalar("id", LongType.INSTANCE)
//Creates a list of the found IDs
#SuppressWarnings("unchecked")
List<Long> ids = q.list();
//Fetches all object with those IDs...
Criteria smallestOfEachGroup = session.createCriteria(Object.class)
.add(Restrictions.in("id", ids);
//...and saves them in a list.
#SuppressWarnings("unchecked")
List<Object> desiredList = smallestOfEachGroup.list()
try{
transaction.commit();
} catch(HibernateException e) {
transaction.rollback();
}
As all my sketches are not the real code, so there might be still naming errors.
Anyway, I hope this helps someone.
I still would be pleased by any more elegant solution.
Update [2014.07.20, 18:50]:
I found a solution that uses Hibernate Criteria exclusively :)
Session session = getSession();
Transaction transaction = session.beginTransaction();
//This subquery fetches the minimal timestamp of a container.
DetachedCriteria minOfGroup = DetachedCriteria.forClass(Object.class);
minOfGroup.add(Restrictions.eqProperty("con.con_id", "outerObject.con.con_id")
.setProjection(Projections.min("timestamp"));
//This subquery fetches the IDs of all Objects, whose timestamp is minimal
//in their container.
DetachedCriteria groupwiseMin = DetachedCriteria.forClass(Object.class, "outerObject");
groupwiseMin.add(Subqueries.propertyEq("timestamp", minOfGroup));
.setProjections(Projections.id())
//This subquery fetches all Objects whose IDs are fetched by the groupwiseMin
//query
Criteria groupwiseMinObjects = session.createCriteria(Object.class);
groupwiseMinObjects.add(Subqueries.propertyIn("obj_id", groupwiseMin));
List<Object> desiredObjects = groupwiseMinObjects.list();
try{
transaction.commit();
} catch(HibernateException e) {
transaction.rollback();
}
I think you can make this query even shorter, if you remove the groupwiseMinObjects query above replace the groupwiseMin query by:
Criteria anotherGroupWiseMinObjects = session.createCriteria(Object.class, "outerObject");
anotherGroupwiseMinObjects.add(Subqueries.propertyEq("timestamp", minOfGroup));
But I did not test that.
In my original project I use several subqueries that converge in a single query.
That means after some subqueries, there is a final query like:
Criteria finalQuery = session.createCriteria(Object.class);
finalQuery.add(Subqueries. (...) )
(...)
.add(Subqueries. (...) );

Neo4j: spring-data-neo4j,how to cast result to my type class?

I have insert some node into Neo4j DB.And I want to select some node from database and cast it to specific class.
Here are some code about the problem:
class Service {
Neo4jTemplate neo4jTemplate
#Transactional
def find() {
def id1 = 11
//Knowledge k = neo4jTemplate.findOne(1, Knowledge)
Result result = neo4jTemplate.query("start n=node(11) return ID(n),n.name,n.age;", null)
//how to cast the result to User class
println "the tpye of result called User is "+ result.to(User.class).is(cn.edu.bnuz.itc.bok.sub2.User.class)
}
}
The detail about node like :
+-------------------------------------------------------------------------+
| Node[11]{career:"programmer",name:"kelvin",age:35,introduce:"lazy doy"} |
+-------------------------------------------------------------------------+
#NodeEntity
class User {
#GraphId
Long id;
String name;
int age;
}
I just want get the node's id, name, age from db and put it into a User class.
But it failed many time with many method.
Here I have encounter a problem which is :How can I cast the result to my target class? I have try many method to cast but fail finally.Thank you for you attention.
Return the user node from the query and call the to method of the returned Result with the desired class as argument:
Result result = neo4jTemplate.query("start n=node(11) return n", null);
for(User u : result.to(User.class)) {
doSomethingWith(u);
}
You may want to consider using repositories that support cypher queries like:
public interface UserRepository extends GraphRepository<User> {
#Query("start n=node(11) return n")
Iterable<User> getUser11();
}

How to get the first member of the related collection in JPQL

I have Product table which has a related table Images with a relation 1:M.
Class Product {
private Integer productId;
private String productName;
....
....
....
private List<Image> productImageList;
....
....
....
}
Class Image{
private Integer imageId;
private String imageName;
}
Class ProductLite{
private Integer productId;
private String productName;
private String imageName;
}
I am trying a JPQL query where I want to query to fetch products and the first image from the productImageList and returning a ProductLite object using the new constructor.
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<ProductLite> getAllProductLite() {
Query q = em.createQuery("SELECT NEW com.mycomp.application.entity.ProductLite(p.productId, p.productName, p.productImageList.get(0).getImageName())"
+ " from Product p"
+ " ORDER by p.productName");
List<ProductLite> prods = q.getResultList();
return prods;
}
But for some reason I am not able to get it to work. I get a NoViableException. So I tried moving the logic of getting the first image (getImage() method) to the Product Entity so in the query I could just call the getImage(). Even that does not seem to work.
java.lang.IllegalArgumentException: An exception occurred while creating a query in EntityManager:
Exception Description: Syntax error parsing the query [SELECT NEW com.meera.application.entity.ProductLite(distinct p.productId, p.productName, p.getImage()) from Product p, IN(p.productImageList) pil where p.category.categoryCode = :categoryCode ORDER by p.productName ], line 1, column 52: unexpected token [distinct].
Internal Exception: NoViableAltException(23#[452:1: constructorItem returns [Object node] : (n= scalarExpression | n= aggregateExpression );])
Any help is appreciated.
First, you cannot call methods in entity class from your JP QL query. Second, to use the order of entities in list, you need persisted order.
To create column for order to the join table between image and product, you have to add
#OrderColumn-annotation to the productImageList. For example:
#OrderColumn(name = "myimage_order")
//or dont't define name and let it default to productImageList_order
#OneToMany
private List<Image> productImageList;
Then you have to modify query to use that order to choose only first image:
SELECT NEW com.mycomp.application.entity.ProductLite(
p.productId, p.productName, pil.imageName)
FROM Product p JOIN p.productImageList pil
WHERE INDEX(pil) = 0
ORDER by p.productName

Need help coding a CriteriaQuery "in" predicate

I have a query that I have been trying to turn into a CriteraQuery, but I don't get how to code the "IN" term of the query.
The JPA Query that works looks like this:
#NamedQuery(name = "User.fromSearchID",
query = "SELECT q FROM User q,"
+ " IN (q.data) AS s WHERE s.data LIKE :search"
+ " ORDER BY q.id")
And the entity that it works on looks like this:
#Entity
public class User {
#Id private Integer id;
#OneToMany private List<UserData> data;
... }
And the referenced entity is
#Entity
public class UserData {
#Id private Long id;
private String key;
private String data;
... }
The intended result (the NamedQuery works) is to pull out all User entities that have some match in their list of data attributes. For some reason the CriteraQuery syntax escapes my intuition. Any help?
I assume users should be unique?
Then JP QL version without rather old fashion IN is:
String searchArgument = "data1";
String query = " SELECT DISTINCT(u) " +
" FROM User u JOIN u.data ud " +
" WHERE ud.data LIKE :search ORDER BY u.id";
List<User> result =em.createQuery(query, User.class).
setParameter("search", searchArgument).getResultList();
And same via Criteria API:
String searchArgument = "data1";
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
Predicate dataLike = cb.like(user.join("data").<String>get("data"),
cb.parameter(String.class, "search"));
cq.distinct(true).select(user)
.where(dataLike)
.orderBy(cb.asc(user.get("id")));
TypedQuery<User> findUniqueUserByUserDataData = em.createQuery(cq);
findUniqueUserByUserDataData.setParameter("search", searchArgument);