List of instances with minimal date of their group - list

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. (...) );

Related

JPQL left Join and Criteria API joins (Left and inner) throwing extra db queries to initialise associations

I am having below two entities .Instructor entity has relationship oneToMany with Vehicle entity.
public class Instructor {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
private int id;
#Version
#Column(columnDefinition = "int(11) not null default 0")
private int version = 0;
#OneToMany(mappedBy = "instructor", orphanRemoval = true, cascade = CascadeType.ALL)
private Set<Vehicle> vehicles = new HashSet<>();
.....
public class Vehicle {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator =
"native")
#GenericGenerator(name = "native", strategy = "native")
private int id;
#ManyToOne(fetch = FetchType.LAZY)
private Student student;
#ManyToOne(fetch = FetchType.LAZY)
private Instructor instructor;
I want to fetch all instructors along with their vehicles in one query .
I have tried below four ways for doing this but i am not able to do this with test case 2 , 3 and 4 .
Test Case 1 : With JPQL join fetch clause.
#Test
#Transactional
#Rollback(false)
public void fetchTest1(){
List<Instructor> instructorsJpqlJoinFetch = em
.createQuery("select distinct i from
Instructor i join fetch i.vehicles v ",
Instructor.class)
.getResultList();
print(instructorsJpqlJoinFetch);
}
private void print(List<Instructor> instructors) {
instructors.forEach(i -> {
System.out.println("######Instructor Name : " + i.getName());
i.getVehicles().forEach(v -> {
System.out.println("######Instructor Vehicle
Number : " + v.getVehicleNumber());
});
});
Db Query gone to DB for case 1 :
select
distinct instructor0_.id as id1_2_0_,
vehicles1_.id as id1_5_1_,
instructor0_.address as address2_2_0_,
instructor0_.birth_date_time as birth_da3_2_0_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_0_,
instructor0_.created_date as created_5_2_0_,
instructor0_.day_off_time as day_off_6_2_0_,
instructor0_.day_start_time as day_star7_2_0_,
instructor0_.father_name as father_n8_2_0_,
instructor0_.mother_name as mother_n9_2_0_,
instructor0_.name as name10_2_0_,
instructor0_.photo as photo11_2_0_,
instructor0_.monthly_salary as monthly12_2_0_,
instructor0_.updated_date as updated13_2_0_,
instructor0_.version as version14_2_0_,
vehicles1_.creation_date as creation2_5_1_,
vehicles1_.instructor_id as instruct8_5_1_,
vehicles1_.purchased_date_time as purchase3_5_1_,
vehicles1_.purchased_date_zone_offset as purchase4_5_1_,
vehicles1_.student_id as student_9_5_1_,
vehicles1_.updated_date as updated_5_5_1_,
vehicles1_.vechicle_type as vechicle6_5_1_,
vehicles1_.vehicle_number as vehicle_7_5_1_,
vehicles1_.instructor_id as instruct8_5_0__,
vehicles1_.id as id1_5_0__
from
instructor instructor0_
inner join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
As a result All instructors are fethched from db along with their vehicles
and after executing i.getVehicles() no query goes to db again . Which should be a correct behaviour . I am getting this behaviour with JPQL join fetch clause .
Test Case 2 : Tried same with Criteria API like below :
#Test
#Transactional
#Rollback(false)
public void fetchTest3() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Instructor> cq =
cb.createQuery(Instructor.class);
Root<Instructor> root = cq.from(Instructor.class);
root.join(Instructor_.vehicles);
List<Instructor> instructorsWithCriteria =
em.createQuery(cq.distinct(true).select(root)).
getResultList();
print(instructorsWithCriteria);
}
Db query gone to db for case 2 :
select distinct instructor0_.id as id1_2_,
instructor0_.address as address2_2_,
instructor0_.birth_date_time as birth_da3_2_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_,
instructor0_.created_date as created_5_2_,
instructor0_.day_off_time as day_off_6_2_,
instructor0_.day_start_time as day_star7_2_,
instructor0_.father_name as father_n8_2_,
instructor0_.mother_name as mother_n9_2_,
instructor0_.name as name10_2_,
instructor0_.photo as photo11_2_,
instructor0_.monthly_salary as monthly12_2_,
instructor0_.updated_date as updated13_2_,
instructor0_.version as version14_2_
from
instructor instructor0_
inner join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
**As a result all instructors are fethched from db . As soon as i hit
i.getVehciles() in print method a query to get vehicles of this instructor goes to db . Same happens for all instructors one by one .
Same behaviour happens for case 3 and 4 below too .
What should be passed in select clause of case 2, 3, 4 so that vehicle columns also got selected in query ?
**
Test case 3: JPQL left join
#Test
#Transactional
#Rollback(false)
public void fetchTest2() {
List<Instructor> instructorsJpqlLeftJoin = em
.createQuery("select distinct i from Instructor i left join
i.vehicles v ", Instructor.class)
.getResultList();
print(instructorsJpqlLeftJoin);
}
Db Query to db for Case 3 :
select
distinct instructor0_.id as id1_2_,
instructor0_.address as address2_2_,
instructor0_.birth_date_time as birth_da3_2_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_,
instructor0_.created_date as created_5_2_,
instructor0_.day_off_time as day_off_6_2_,
instructor0_.day_start_time as day_star7_2_,
instructor0_.father_name as father_n8_2_,
instructor0_.mother_name as mother_n9_2_,
instructor0_.name as name10_2_,
instructor0_.photo as photo11_2_,
instructor0_.monthly_salary as monthly12_2_,
instructor0_.updated_date as updated13_2_,
instructor0_.version as version14_2_
from
instructor instructor0_
left outer join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
Case 4 : Criteria API Left Join :
#Test
#Transactional
#Rollback(false)
public void fetchTest4() {
CriteriaBuilder cbLeftJoin = em.getCriteriaBuilder();
CriteriaQuery<Instructor> cqLeftJoin =
cbLeftJoin.createQuery(Instructor.class);
Root<Instructor> rootLeftJoin = cqLeftJoin.from(Instructor.class);
rootLeftJoin.join(Instructor_.vehicles, JoinType.LEFT);
List<Instructor> instructorsWithCriteriaLeftJoin = em
.createQuery(cqLeftJoin.distinct(true).
select(rootLeftJoin)).getResultList();
print(instructorsWithCriteriaLeftJoin);
}
Db Query for case 4 :
select
distinct instructor0_.id as id1_2_,
instructor0_.address as address2_2_,
instructor0_.birth_date_time as birth_da3_2_,
instructor0_.birth_date_time_zone_offset as birth_da4_2_,
instructor0_.created_date as created_5_2_,
instructor0_.day_off_time as day_off_6_2_,
instructor0_.day_start_time as day_star7_2_,
instructor0_.father_name as father_n8_2_,
instructor0_.mother_name as mother_n9_2_,
instructor0_.name as name10_2_,
instructor0_.photo as photo11_2_,
instructor0_.monthly_salary as monthly12_2_,
instructor0_.updated_date as updated13_2_,
instructor0_.version as version14_2_
from
instructor instructor0_
left outer join
vehicle vehicles1_
on instructor0_.id=vehicles1_.instructor_id
What should i do in case 2,3,4 so that vehicles columns also got selected in the same query so that no secondory selects go to db?

How to update item by Composite Primary Key in Dynamodb

I have a table called friends:
Friend 1 | Friend 2 | Status
Friend 1 is my HASH attribute and Friend 2 is my range attribute.
I would like to update an item's staus attribute where friend 1 = 'Bob' and friend 2 = 'Joe'. Reading through the documentation on http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/JavaDocumentAPICRUDExample.html I can only see how to update an item by 1 key, how do I include the other key?
Here you go:
DynamoDBQueryExpression<Reply> queryExpression = new DynamoDBQueryExpression<Reply>()
.withKeyConditionExpression("Id = :val1 and ReplyDateTime > :val2")
.withExpressionAttributeValues(
...
where Id is the Hash Key and ReplyDateTime is the Range Key.
Reference:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.QueryScanExample.html
I'm writing example where you can make update of multiple item in single table. I have primary key as id and range key as Datetime.
Actually there is no feature available in dynamodb so what i'm doing here is first query all the variable with hash key and range key of which i want to make update. Once all data are stored in List then loading data with it's hash key and rangekey and changing or updating field using set and save it.
Since I'm editing in hash key so, hash key original will be there we need to delete it. If you need to update in next attribute no need. I haven't added deleting code write yourself. You can query if you have confusion your entry with hash key will be still and new entry with new hash key will be added.
Code is below:
public static void main(String[] args) {
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
DynamoDBMapper mapper = new DynamoDBMapper(client);
client.setEndpoint("http://localhost:8000/");
String fromDate = "2016-01-13";
String toDate = "2016-02-05";
User user = new User();
user.setId("YourHashKey");
LocalDate frmdate = LocalDate.parse(fromDate, DateTimeFormatter.ISO_LOCAL_DATE);
LocalDate todate = LocalDate.parse(toDate, DateTimeFormatter.ISO_LOCAL_DATE);
LocalDateTime startfrm = frmdate.atStartOfDay();
LocalDateTime endto = todate.atTime(23, 59, 59);
Condition rangeCondition = new Condition().withComparisonOperator(ComparisonOperator.BETWEEN.toString()).withAttributeValueList(new AttributeValue().withS(startfrm.toString()), new AttributeValue().withS(endto.toString()));
DynamoDBQueryExpression<User> queryExpression = new DynamoDBQueryExpression<User>().withHashKeyValues(user).withRangeKeyCondition("DATETIME", rangeCondition);
List<User> latestReplies = mapper.query(User.class, queryExpression);
for (User in : latestReplies) {
System.out.println(" Hashid: " + in.getId() + " DateTime: " + in.getDATETIME() + "location:" + in.getLOCID());
User ma = mapper.load(User.class, in.getId(), in.getDATETIME());
ma.setLOCID("Ohelig");
mapper.save(ma);
}
}

count * with where clause using JPA criteria query

I want to find records for related_elements table, where relationId belongs to a list
suppose a Set tempSet contains[2,3,4]
I have to check for these value contains in related_element table using jpa criteria query
CriteriaBuilder cb1=entityManager.getCriteriaBuilder();
CriteriaQuery<RelatedElements> cq1=cb1.createQuery(RelatedElements.class);
Root<RelatedElements> RelatedElementsRoot=cq1.from(RelatedElements.class);
for (Integer tSet : tempSet) {
ParameterExpression<Integer> pRelatedElement=cb1.parameter(Integer.class);
cq1.multiselect(cb1.count(RelatedElementsRoot.<RelatedElements>get("relatedElementsPk").<Integer>get("relationId"))).where(cb1.equal(RelatedElementsRoot.get("relationId"), pRelatedElement));
TypedQuery<RelatedElements> qry = entityManager.createQuery(cq1);
qry.setParameter(pRelatedElement, tSet);
count = entityManager.createQuery(cq1).getSingleResult().getRelationId();
}
but its now working...any suggessions
second try
CriteriaBuilder cb1=entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> cq1 = cb1.createQuery(Integer.class);
Root<RelatedElements> RelatedElementsRoot=cq1.from(RelatedElements.class);
for (Integer tSet : tempSet) {
ParameterExpression<Integer> pRelatedElement=cb1.parameter(Integer.class);
cq1.multiselect(cb1.count(cq1.from(RelatedElements.class)));
cq1.where((cb1.equal(RelatedElementsRoot.get("relatedElementsPk").get("relationId"), pRelatedElement)));
TypedQuery<Integer> qry = entityManager.createQuery(cq1);
qry.setParameter(pRelatedElement, tSet);
count =qry.getSingleResult();
}
its giving exception at qry.setParameter
Unable to locate appropriate constructor on class [java.lang.Integer] [select new java.lang.Integer(count(*)) from com.mcd.webex.model.RelatedElements as generatedAlias0, com.mcd.webex.model.RelatedElements as generatedAlias1 where generatedAlias0.relatedElementsPk.relationId=:param0]
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<Dzialy> root = cq.from(Dzialy.class);
EntityType <Dzialy> Dzialy_ = root.getModel();
cq.select((cb.countDistinct(root)));
cq.where( cb.equal( root.get(Dzialy_.getSingularAttribute("DZI_id")), 1) );
long l = em.createQuery(cq).getSingleResult();
em - EntityManager
DZI_id - column name
1 - searching value
Dzialy - entity class
As documented, CriteriaBuilder.count returns Expression<java.lang.Long>. Consequently type argument to CriteriaQuery and TypedQuery should be Long as well. Same holds true for type of count variable.
When there is only one value to be selected, then it makes sense to use CriteriaQuery.select instead of multiselect, because then such an error is catched already in compile time.
Long count;
...
CriteriaQuery<Long> cq1 = cb1.createQuery(Long.class);
...
cq1.select(cb1.count(cq1.from(RelatedElements.class)));
...
TypedQuery<Long> qry = entityManager.createQuery(cq1);
CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = qb.createQuery(Long.class);
cq.select(qb.count(cq.from(YourEntity.class)));
cq.where(/*your stuff*/);
return entityManager.createQuery(cq).getSingleResult();

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);