I have two entities Issue and Issue_Tracker. I am using hibernate 3.6 and one to many association.
Issue.java
public class Issue implements Serializable {
private Integer issue_id;
private String issue_description;
private Date issue_raised_date;
private Set<Issue_Tracker> issueTracker = new HashSet<Issue_Tracker>(0);
#OneToMany(fetch=FetchType.LAZY, mappedBy="issue_id")
public Set<Issue_Tracker> getIssueTracker() {
return issueTracker;
}
public void setIssueTracker(Set<Issue_Tracker> issueTracker) {
this.issueTracker = issueTracker;
Issue_Tracker.java
public class Issue_Tracker implements Serializable
{
private Integer issue_id;
private String tracker_status;
private Timestamp tracked_time;**
And this is the sql query, how to achieve this using criteria
SELECT i.issue_id, i.issue_description,
it.tracker_status, it.tracked_time
FROM issues i
LEFT JOIN ( SELECT it.issue_id, it.tracker_status, it.tracked_time
FROM issue_tracker it
INNER JOIN (SELECT issue_id, MAX(tracked_time) tracked_time
FROM issue_tracker GROUP BY issue_id
) A ON it.issue_id = A.issue_id AND it.tracked_time = A.tracked_time
) it ON i.issue_id = it.issue_id
WHERE i.status = "Escalate To";
First of all I suggest you change the confusing name of Issue.issueTracker to Issue.issueTrackers since it is a Set. It makes things easier to read when you are querying. But whatever.
In Criteria API I do not think you can directly translate this SQL. You are better off writing a description of what result set you want. Do you want the last tracked time for all issues with "Escalate To" status? If so this should be close to what you want.
DetachedCriteria inner = DetachedCriteria.forClass(IssueTracker.class, "inner")
.setProjection(Projections.max("inner.tracked_time"));
Criteria crit = session.createCriteria(Issue.class, "issue");
// Add the join with an ON clause (I am not sure why you need the LEFT JOIN)
crit.createAlias("issueTracker", "it", Criteria.LEFT_JOIN,
Subqueries.eqProperty("it.tracked_time", inner));
// Specify the SELECT fields
crit.setProjections(Projections.projectionList()
.add(Projections.property("issue_id"))
// etc
.add(Projections.property("it.tracked_time"));
crit.add(Restrictions.eq("status", "Escalate To");
List<Object[]> rows = crit.list();
Related
I have a spring app with the user entity and the users table. I would like to get a number of all users grouped by certain fields (not per group but in total).
In sql It would be:
select
count(*) OVER () as totalRecords
from users u
group by
u.first_name,
u.last_name,
u.age
order by u.age DESC
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY;
But I really can't do that using hibernate criteria. I could do something like:
public Long getTotalCount() {
ProjectionList groupBy = projectionList();
groupBy.add(groupProperty("firstName"), "first_name");
groupBy.add(groupProperty("last_name"), "last_name");
groupBy.add(groupProperty("age"), "age");
groupBy.add(Projections.rowCount());
return (Long) getSession().createCriteria("User")
.setProjection(groupBy)
.uniqueResult();
}
but it's not what I want. It does counting per each group, I would like to count rows that are the result of the group by clause
I just spend couple hours trying to find out a way and finally got it working.
Disclaimer
It is impossible to do an optimal query with plain criteria API. Optimal would be either SELECT COUNT(*) FROM ( group by query here ) or SELECT COUNT(*) OVER (). Neither is possible. To get an optimal query, use plain SQL if possible. For my case using plain SQL was not possible, because I have constructed a very complex logic that builds criteria and I want to use the same logic for resolving the count of aggregate also (to resolve count of pages for pagination).
Solution
First we add the following to all Entities that are used as base of criteria:
#Entity
class MyEntity {
private Long aggregateRowCount;
#Formula(value="count(*) over()")
public Long getAggregateRowCount() {
return aggregateRowCount;
}
public void setAggregateRowCount(Long aggregateRowCount) {
this.aggregateRowCount = aggregateRowCount;
}
Criteria building looks like this:
Criteria criteria = // construct query here
ProjectionList projectionList = // construct Projections.groupProperty list here
projectionList.add(Projections.property("aggregateRowCount")); // this is our custom entity field with the #Formula annotation
criteria.setProjection(projectionList);
criteria.setMaxResults(1);
criteria.setResultTransformer(AggregatedCountResultTransformer.instance());
List<?> res = builder.criteria.list();
if (res.isEmpty()) return 0L;
return (Long) res.get(0);
This generates SQL that looks like this:
SELECT groupbyfield1, groupbyfield2, count(*) over()
FROM ...
GROUP BY groupbyfield1, groupbyfield2
LIMIT 1;
Without LIMIT 1 the result would be
field1 | field2 | count
a | b | 12356
a | c | 12356
... | ... | 12356
but we add the LIMIT 1 (criteria.setMaxResults(1);) because the first row already contains the number of rows and that is all we need.
Finally, our AggegatedCountResultTransformer:
class AggregatedCountResultTransformer implements ResultTransformer {
private static final AggregatedCountResultTransformer instance = new AggregatedCountResultTransformer();
public static ResultTransformer instance() {
return instance;
}
#Override
public Object transformTuple(Object[] values, String[] fields) {
if (values.length == 0) throw new IllegalStateException("Values is empty");
return values[values.length-1]; // Last value of selected fields (the count)
}
#SuppressWarnings("rawtypes")
#Override
public List transformList(List allResults) {
return allResults; // This is not actually used?
}
I want to update value to a specific column entered by the user. Here are my codes, do anyone how to modify it to correct one?
public void updateValue(String value, String id, String ww){
Query q = em.createQuery("Update TableA e SET e.?1 = ?2 WHERE e.num = ?3");
q.setParameter(1, ww); //since has many columns, user require to specific column to update
q.setParameter(2, value);
q.setParameter(3, id);
q.executeUpdate();
}
You should go for criteria builder query for your case... if you are using JPA 2.1.. here is something you can should do
public void updateValue(String value, String id, String ww){
CriteriaBuilder cb = this.em.getCriteriaBuilder();
// create update
CriteriaUpdate<TableAEntity> update = cb.
createCriteriaUpdate(TableAEntity.class);
// set the root class
Root e = update.from(TableAEntity.class);
// set update and where clause
update.set(ww, value);
update.where(cb.equalTo(e.get("num"),
id));
// perform update
this.em.createQuery(update).executeUpdate();
}
You asked
Updating one column based on predicates (and possibly more advance operations).
More detail and Clean practice
The best practice for criteria base operations like updates is to use javax.persistence.criteria interfaces, like CriteriaUpdate and using where() clause restriction upon predicates.
Further more with respect of your predicates and column types you can use CriteriaBuilder api's for many operations and aggregations, like appending other columns or values to desired column (path).
CriteriaUpdate<T> criteriaUpdate = criteriaBuilder.createCriteriaUpdate(type);
Root<T> updateRoot = criteriaUpdate.from(type);
Path<String> path = updateRoot.get(update_column);
criteriaUpdate.set(path, 'some_value');
entityManager.createQuery(criteriaUpdate.where(...)).executeUpdate()
Note that the some_value could even be calculated with nested operations and aggregations with help of criteriaBuilder like
criteriaUpdate.set(path, criteriaBuilder.api(...));
Summarizing in your case it will look like below
CriteriaUpdate<TableAEntity> criteriaUpdate = builder.createCriteriaUpdate(TableAEntity.class);
Path<String> path = root.get("ww");
criteriaUpdate.set(path, value);
entityManager.createQuery(criteriaUpdate.where(criteriaBuilder.equal(root.get("num"), id) )).executeUpdate()
...
I have a hibernate class like this:
public class UserActivityLog implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private Users users;
private Servers servers;
private Date time;
private String event;
}
I sort a collection of UserActivityLog objects, using hibernate sorting. here's my hibernate criteria:
criteria.add(Restrictions.ge(sortField, new Timestamp(startDate.getTime())));
//add one day to the real end date for it to be considered in criteria
Date eDate = getEndDate(endDate);
criteria.add(Restrictions.le(sortField, new Timestamp(eDate.getTime())));
if (searchByUser >= 0) {
criteria.add(Restrictions.eq("users.id", searchByUser));
}
if (searchByHostId >= 0) {
criteria.add(Restrictions.eq("servers.id", searchByHostId));
}
if (!searchByEvent.isEmpty()) {
criteria.add(Restrictions.like("event", searchByEvent, MatchMode.ANYWHERE));
}
if(sortColumn.equals("username")) {
sortColumn = "users.username";
criteria.createAlias("users", "users");
}
else if (sortColumn.equals("hostName")) {
sortColumn = "servers.hostName";
criteria.createAlias("servers", "servers");
}
//specify the sorting oder
if(SortDirection.asc.equals(sortDirection)) {
criteria.addOrder(Order.asc(sortColumn));
} else {
criteria.addOrder(Order.desc(sortColumn));
}
List<Object> allRows = (ArrayList<Object>) criteria.list();
The servers property of the UserActivityLog can be null. When the collection has an object with servers null, and if I sort the collection using hostName which is a property in the Servers object, the sorted collection does not contain that object with servers = null. Is there any reason for that or am I doing something wrong?
Update:
Here is the hibernate query:
select count(*) as y0_ from USER_ACTIVITY_LOG this_ inner join SERVERS servers1_ on this_.HOST_ID=servers1_.ID
where this_.TIME>='2014/9/16' and this_.TIME<='2014/9/25' order by servers1_.HOST_NAME asc
I would guess that the objects with servers = null is not been fetched from the database because of:
if (searchByHostId >= 0) {
criteria.add(Restrictions.eq("servers.id", searchByHostId));
}
Not because of the sort.
I found both the root cause and the solution:
When I inspect the hibernate query corresponding to this sort, it has an inner join with the Servers table (the query is shown in the question). When there's a log entry with the server = null, the join fails therefore that record is not displayed.
What I did to fix the issue is to force a LEFT JOIN for that sort column:
if (sortColumn.equals("hostName")) {
sortColumn = "servers.hostName";
criteria.createAlias("servers", "servers", JoinType.LEFT_OUTER_JOIN);
}
in Hibernate is there a way to return an object or list of objects that have derived data that isn't persisted in the table as part of an object?
for example if you had an object of tweets as follows:
long id;
String message;
long profile_id;
//non-persisted properties
long numLikes;
boolean isLiked;
then you had another object to keep track of who has liked the tweet such as
long id;
long liked_id;
long profile_id;
boolean liked;
how would I (can I) set up the tweet object so that I could see a count of likes inside the tweet object? the query would look something like
Select *, count(likes1.id) as numLikes, isNull(liked1.liked,0) as isLiked from tweets
left join (select id,liked_id from likes where liked_id = tweets.id) as likes1 on tweets.id = likes1 .liked_id
left join (select liked_id,liked from likes where profile_id = :authenticated_User) as liked1 on tweets.id = liked1.liked_id
where.....
is there anyway I can stuff all of this in an object without using addScalar on every property in the tweets object? if not what is the proper way of doing this kind of set-up?
(assuming all properties are named correctly in sql query and data is returned as expected I know there are things in my example that will break.)
My suggestion doing it this way (use it only in case you do not use DTOs):
1) create class TweetRecordFilter to help filter TweetRecord
public class TweetRecordFilter(){
private long tweetId;
private long personId;
private long profileId;
private boolean liked;
//create setters and getters for all attributes mentioned above
}
2) create method for finding TweetRecords (you said they keep track of who like what)
public List<TweetRecord> findTweetRecordByFilter(TweetRecordFilter tweetrecordFilter){
// return found records that have properties that were set to tweetRecordFilter
// I suggest do a select using PreparedStatement
/* here write that code*/
}
3) create method for selecting Tweets
public Tweet findTweetById(int tweetId){
// find tweet that has tweetId and say you have found one, let's call it "foundTweet"
// I suggest do a select using PreparedStatement
/* here write that code*/
// find all tweetRecord that have specific profile_id
TweetRecordFilter tweetRecordFilter = new TweetRecordFilter();
tweetRecordFilter.setProfileId(foundTweet.getProfileId);
tweetRecordFilter.setLiked(true);
List<TweetRecord> tweetRecords = findTweetRecordByFilter(tweetRecordFilter);
// set the number of likes for tweet
if(tweetRecords != null){
foundTweet.setLikesCount(tweetRecords.size());
}
return foundTweet;
}
So all put together in a practical example:
int tweetId = 1;
Tweet tweet = findTweetById(1);
int numberOfLikes = tweet.getNumberOfLikes;
More on preparedStatement you can find here.
I'm developing a multilingual application. For this reason many objects have in their name and description fields collections of something I call LocalizedStrings instead of plain strings. Every LocalizedString is basically a pair of a locale and a string localized to that locale.
Let's take an example an entity, let's say a book -object.
public class Book{
#OneToMany
private List<LocalizedString> names;
#OneToMany
private List<LocalizedString> description;
//and so on...
}
When a user asks for a list of books, it does a query to get all the books, fetches the name and description of every book in the locale the user has selected to run the app in, and displays it back to the user.
This works but it is a major performance issue. For the moment hibernate makes one query to fetch all the books, and after that it goes through every single object and asks hibernate for the localized strings for that specific object, resulting in a "n+1 select problem". Fetching a list of 50 entities produces about 6000 rows of sql commands in my server log.
I tried making the collections eager but that lead me to the "cannot simultaneously fetch multiple bags"-issue.
Then I tried setting the fetch strategy on the collections to subselect, hoping that it would do one query for all books, and after that do one query that fetches all LocalizedStrings for all the books. Subselects didn't work in this case how i would have hoped and it basically just did exactly the same as my first case.
I'm starting to run out of ideas on how to optimize this.
So in short, what fetching strategy alternatives are there when you are fetching a collection and every element in that collection has one or multiple collections in itself, which has to be fetch simultaneously.
You said
I tried setting the fetch strategy on the collections to subselect, hoping that it would do one query for all books
You can, but you need to access some property to throw the subselect
#Entity
public class Book{
private List<LocalizedString> nameList = new ArrayList<LocalizedString>();
#OneToMany(cascade=javax.persistence.CascadeType.ALL)
#org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
public List<LocalizedString> getNameList() {
return this.nameList;
}
private List<LocalizedString> descriptionList = new ArrayList<LocalizedString>();
#OneToMany(cascade=javax.persistence.CascadeType.ALL)
#org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
private List<LocalizedString> getDescriptionList() {
return this.descriptionList;
}
}
Do as follows
public class BookRepository implements Repository {
public List<Book> getAll(BookFetchingStrategy fetchingStrategy) {
switch(fetchingStrategy) {
case BOOK_WITH_NAMES_AND_DESCRIPTIONS:
List<Book> bookList = session.createQuery("from Book").list();
// Notice empty statement in order to start each subselect
for (Book book : bookList) {
for (Name address: book.getNameList());
for (Description description: book.getDescriptionList());
}
return bookList;
}
}
public static enum BookFetchingStrategy {
BOOK_WITH_NAMES_AND_DESCRIPTIONS;
}
}
I have done the following one to populate the database
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
// Ten books
for (int i = 0; i < 10; i++) {
Book book = new Book();
book.setName(RandomStringUtils.random(13, true, false));
// For each book, Ten names and descriptions
for (int j = 0; j < 10; j++) {
Name name = new Name();
name.setSomething(RandomStringUtils.random(13, true, false));
Description description = new Description();
description.setSomething(RandomStringUtils.random(13, true, false));
book.getNameList().add(name);
book.getDescriptionList().add(description);
}
session.save(book);
}
session.getTransaction().commit();
session.close();
And to retrieve
session = sessionFactory.openSession();
session.beginTransaction();
List<Book> bookList = session.createQuery("from Book").list();
for (Book book : bookList) {
for (Name address: book.getNameList());
for (Description description: book.getDescriptionList());
}
session.getTransaction().commit();
session.close();
I see
Hibernate:
select
book0_.id as id0_,
book0_.name as name0_
from
BOOK book0_
Hibernate: returns 100 rows (as expected)
select
namelist0_.BOOK_ID as BOOK3_1_,
namelist0_.id as id1_,
namelist0_.id as id1_0_,
namelist0_.something as something1_0_
from
NAME namelist0_
where
namelist0_.BOOK_ID in (
select
book0_.id
from
BOOK book0_
)
Hibernate: returns 100 rows (as expected)
select
descriptio0_.BOOK_ID as BOOK3_1_,
descriptio0_.id as id1_,
descriptio0_.id as id2_0_,
descriptio0_.something as something2_0_
from
DESCRIPTION descriptio0_
where
descriptio0_.BOOK_ID in (
select
book0_.id
from
BOOK book0_
)
Three select statements. No "n + 1" select problem. Be aware i am using property access strategy instead of field. Keep this in mind.
You can set a batch-size on your bags, when one unitialized collection is initialized, Hibernate will initialize a some other collections with a single query
More in the Hibernate doc