Restricting hibernate's eager fetch beyond DAO - java

I have my entities as ProductType,Product and ProductInventory.
I have a join query to fetch list of inventory for a specific date range which joins Product and ProductInventory. I've got list of object arrays which I have casted and set it ready.
Now from DAO I return the list of products.
In my layer above, if I execute product.getProductInventory() it is actually firing a query again getting all the inventory and not those inventory as got by the join.
final StringBuilder queryString = new StringBuilder(
"from Product As rsProduct left outer join rsProduct.inventoryList "
+ "as inventory where rsProduct.efDate <= :travelEndDate AND rsProduct.expDate >= :travelStartDate AND rsProduct.locatiion = :LOCN AND rsProduct.id in (:productsIdList) and inventory.bookDate between :startDate and :endDate");
Ex. Say travel start date is 20th Jan and travel end date is 21 Jan. I get only two records here which is perfect.
But after i return to other layer, if i say product.getInventory() it fetches all inventory irrespective of dates.
Can someone address this problem?

You should define a filter and enable it before accessing the collection.

Related

The query in HibernateSearch does not sort by updated data

I'm using HibernateSearch 5.7.1.Final with ElasticSearch from Java.
I have two main types of objects in my model,
Book and Journal, which inherit from class Record
and share some common properties: rank and title.
The typical rank for a book is 1 and typical value for a journal
is 2, but sometimes it can be different.
I created some data with default rank, but for one book
I wanted to test if updates to the rank value will be respected
in the query results.
I first set it value to 0 before saving:
...
Book book03 = new Book("Special Book", prus);
book03.setRank(0);
session.save(book03);
Then I obtain the object using Hibernate (not HibernateSearch)
mechanisms, and update the value:
session.clear();
session.beginTransaction();
Criteria specialCriteria = session.createCriteria(Book.class);
Book special = (Book) specialCriteria.add(Restrictions.eq("title", "Special Book")).uniqueResult();
special.setRank(6);
session.saveOrUpdate(special);
session.getTransaction().commit();
Now I want to obtain all books and journals and sort them by rank.
I combine a query for books and a query for journals using a boolean query.
I don't know how to obtain all objects of a specific type, without including any criteria, so I query
for objects that have anything in a title.
session.clear();
sessionFactory.getCache().evictEntityRegions();
FullTextSession fullTextSession = Search.getFullTextSession(session);
fullTextSession.clear();
QueryBuilder bookQueryBuilder = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(Book.class).get();
QueryBuilder journalQueryBuilder = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(Journal.class).get();
QueryBuilder generalQueryBuilder = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(Record.class).get();
org.apache.lucene.search.Query bookLuceneQuery
= bookQueryBuilder.keyword().wildcard().onField("title").matching("*").createQuery();
org.apache.lucene.search.Query journalLuceneQuery
= journalQueryBuilder.keyword().wildcard().onField("title").matching("*").createQuery();
org.apache.lucene.search.Query combinedLuceneQuery = generalQueryBuilder
.bool()
.should( bookLuceneQuery )
.should( journalLuceneQuery )
.createQuery();
FullTextQuery combinedQuery = fullTextSession.createFullTextQuery(combinedLuceneQuery, Record.class);
Sort sort = generalQueryBuilder.sort()
.byField("rank").asc()
.createSort();
combinedQuery.setSort(sort);
List result = combinedQuery.list();
System.out.println("\n\nSearch results for books and journals:");
for(Object object : result)
{
Record record = (Record) object;
System.out.println(record.getTitle());
System.out.println(record.getRank());
System.out.println();
}
The problem is that I obtain the following results:
Special Book
6
book01
1
book02
1
book04
1
journal01
2
journal02
2
It looks like the value finally updated to 6
(this value is displayed when printing the object),
but during sorting the value 0 is used
(that's why the Special Book got to the top).
As you can see I tried resetting the session so that nothing gets cached,
but it didn't help.
My another hypothesis is that the rank is not the only element
that affects the sorting, but also some relevance is taken into consideration,
because the problem does not occur when I don't use the boolean
query. When I only ask for books, Special Book is the last
in the result list. But I would prefer to use the combined query
so that I can paginate the results.
By default Elasticsearch (and Hibernate Search, when it uses Elasticsearch) is near real-time, meaning there will be a slight delay (1 second by default) before your updates affect the results of search queries. The moment when updates are taken into account is called a "refresh".
Did you try adding a slight pause (e.g. 2s) after your update?
If it solves the issue, then the refresh delay was causing it. In real-world situation the delay may not be a problem, because you will rarely perform an update and then do a query just after that requires the index to be perfectly up to date.
If you do have such requirements though, you can set the property hibernate.search.default.elasticsearch.refresh_after_write to true. Be aware that this will impact performance negatively, though.

MySQL List data where they share the same name/id

Print each movie name, then print a list of theater IDs and screens where it is playing/has played. For example, for the movie with ID=10, the output will look like
Frankenstein: (7, 17); (314, 73); (4345, 1)
This must be done for all movies. If a movie does not have any showings, you may show it with the list empty, or simply not show it at all (either will be considered correct for this problem).
Im having a hard time figuring out how to select the name then list out (TheaterID, Screen#)
Here are what the tables look like
I started todo the following from another example I found but doesn't seem like it would work.
SELECT movies.MOVIENAME, playing.THEATERID, playingSCREEN.#, count(*) as seqnum
FROM movies, playing
LEFT OUTER JOIN
movies.MOVIEID = playing.MOVIEID
ON p.plantid = pprev.plantid AND p.name >= pprev.name
GROUP BY p.plantid, p.name
Playing when MOVIEID is set to 93
Movies when MOVIEID is set to 93
You can use the following. Also I think it's a bad practice to have # in your column name.
SELECT aa.MOVIEID, aa.MOVIENAME, bb.SCREEN, bb.THEATERID
FROM movie AS aa
LEFT JOIN playing AS bb
ON aa.MOVIDEID = bb.MOVIEID
ORDER BY aa.MOVIEID;

Avoid Date Overlapping using JPA

I have an entity class with properties 'code','fromDate' and 'toDate' and i need to insert one new record using JPA such a way that for given code date range should not overlap.
For example
If code- ABC of date range 01/Feb/2014-10/Feb/2014 exist in DB.
I am inserting code ABC again with date range
03/Feb/2014-07/Feb/2014 should not accept - from date and to date is Within existing Date range
28/Jan/2014-02/Feb/2014 should not accept - to date is Within existing Date range
05/Feb/2014-21/Feb/2014 should not accept - From date is Within existing Date range
01/Jan/2014-28/Feb/2014 should not accept - The existing date range is within the given date range so Overlapping will happen.
Suppose the data need to be inserted is in a viewObject with similar properties.
Please help me to do the validation for date overlapping using JPA predicates
Before saving the new object you can query the DB to check if an 'overlapping' records exists.
If, a record is returned, then do not save the new object, else save;
String query = "SELECT ent FROM Entity ent WHERE ent.fromDate <= :toDate AND ent.toDate >= :fromDate WHERE ent.id = :entId";
List<Entity> overlappingRecords = JPA.em().createQuery(query).setParameter("entId", id).setParameter("fromDate", fromDate).setParameter("toDate", toDate).getResultList();
if(overlappingRecords.isEmpty())
//Over lap does not exist
else
//Over lap exists
This query assumes rejection of edges overlapping exactly.

db4o query optimisation for a scheduling application

I'm just getting started with db4o in a scheduling application and I'm looking for an efficient way to retrieve rooms which are not booked between certain dates.
So, I have a collection of Room objects each of which has a collection of Booking objects (which can be empty). A Booking has a start date and and end date. I want to say 'get all the rooms that have no Bookings between DateA and DateB'.
I'm sure I could do this using a Native Query but since there's a date range involved (my understanding is date ranges aren't optimzed for NQ) and I need to do this query very frequently (many times per second for potentially more 10,000 rooms - the majority of which have no Bookings) I'm looking for more efficient alternatives.
Is there a way to phrase this using SODA?
Or a better way to arrange my data model to get round this issue?
Yes you can do this by using SODA Query
Date fromDate = null ; // assign reservation start dat
Date toDate = null ; // assign reservation upto
Query query = db.query();
query.constrain(Booking.class);
query.descend ("fromDate").constrain ( fromDate ).greater().equal ().
and (query.descend ("toDate").constrain (toDate).smaller().equal());
ObjectSet<Booking> objectSet = query.execute();
Query for all of the rooms which do not have a booking between fromDate and toDate
Query query = db.query();
query.constrain(Room.class);
query.descend ("bookingStartDate").constrain ( fromDate ).greater().equal ().and
(query.descend ("bookingEndDate").constrain (toDate).smaller().equal()).not();
ObjectSet<Room> objectSet = query.execute();
See Also : Building SODA Queries

Loading multiple entities by id efficiently in Hibernate

So, I'm getting a number of instances of a particular entity by id:
for(Integer songId:songGroup.getSongIds()) {
session = HibernateUtil.getSession();
Song song = (Song) session.get(Song.class,id);
processSong(song);
}
This generates a SQL query for each id, so it occurred to me that I should do this in one, but I couldn't find a way to get multiple entities in one call except by running a query. So I wrote a query
return (List) session.createCriteria(Song.class)
.add(Restrictions.in("id",ids)).list();
But, if I enable 2nd level caching doesn't that mean that my old method would be able to return the objects from the 2nd level cache (if they had been requested before) but my query would always go to the database.
What the correct way to do this?
What you're asking to do here is for Hibernate to do special case handling for your Criteria, which is kind of a lot to ask.
You'll have to do it yourself, but it's not hard. Using SessionFactory.getCache(), you can get a reference to the actual storage for cached objects. Do something like the following:
for (Long id : allRequiredIds) {
if (!sessionFactory.getCache().containsEntity(Song.class, id)) {
idsToQueryDatabaseFor.add(id)
} else {
songs.add(session.get(Song.class, id));
}
}
List<Song> fetchedSongs = session.createCriteria(Song.class).add(Restrictions.in("id",idsToQueryDatabaseFor).list();
songs.addAll(fetchedSongs);
Then the Songs from the cache get retrieved from there, and the ones that are not get pulled with a single select.
If you know that the IDs exist, you can use load(..) to create a proxy without actually hitting the DB:
Return the persistent instance of the given entity class with the given identifier, obtaining the specified lock mode, assuming the instance exists.
List<Song> list = new ArrayList<>(ids.size());
for (Integer id : ids)
list.add(session.load(Song.class, id, LockOptions.NONE));
Once you access a non-identifier accessor, Hibernate will check the caches and fallback to DB if needed, using batch-fetching if configured.
If the ID doesn't exists, a ObjectNotFoundException will occur once the object is loaded. This might be somewhere in your code where you wouldn't really expect an exception - you're using a simple accessor in the end. So either be 100% sure the ID exists or at least force a ObjectNotFoundException early where you'd expect it, e.g. right after populating the list.
There is a difference between hibernate 2nd level cache to hibernate query cache.
The following link explains it really well: http://www.javalobby.org/java/forums/t48846.html
In a nutshell,
If you are using the same query many times with the same parameters then you can reduce database hits using a combination of both.
Another thing that you could do is to sort the list of ids, and identify subsequences of consecutive ids and then query each of those subsequences in a single query. For example, given List<Long> ids, do the following (assuming that you have a Pair class in Java):
List<Pair> pairs=new LinkedList<Pair>();
List<Object> results=new LinkedList<Object>();
Collections.sort(ids);
Iterator<Long> it=ids.iterator();
Long previous=-1L;
Long sequence_start=-1L;
while (it.hasNext()){
Long next=it.next();
if (next>previous+1) {
pairs.add(new Pair(sequence_start, previous));
sequence_start=next;
}
previous=next;
}
pairs.add(new Pair(sequence_start, previous));
for (Pair pair : pairs){
Query query=session.createQuery("from Person p where p.id>=:start_id and p.id<=:end_id");
query.setLong("start_id", pair.getStart());
query.setLong("end_id", pair.getEnd());
results.addAll((List<Object>)query.list());
}
Fetching each entity one by one in a loop can lead to N+1 query issues.
Therefore, it's much more efficient to fetch all entities at once and do the processing afterward.
Now, in your proposed solution, you were using the legacy Hibernate Criteria, but since it's been deprecated since Hibernate 4 and will probably be removed in Hibernate 6, so it's better to use one of the following alternatives.
JPQL
You can use a JPQL query like the following one:
List<Song> songs = entityManager
.createQuery(
"select s " +
"from Song s " +
"where s.id in (:ids)", Song.class)
.setParameter("ids", songGroup.getSongIds())
.getResultList();
Criteria API
If you want to build the query dynamically, then you can use a Criteria API query:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Song> query = builder.createQuery(Song.class);
ParameterExpression<List> ids = builder.parameter(List.class);
Root<Song> root = query
.from(Song.class);
query
.where(
root.get("id").in(
ids
)
);
List<Song> songs = entityManager
.createQuery(query)
.setParameter(ids, songGroup.getSongIds())
.getResultList();
Hibernate-specific multiLoad
List<Song> songs = entityManager
.unwrap(Session.class)
.byMultipleIds(Song.class)
.multiLoad(songGroup.getSongIds());
Now, the JPQL and Criteria API can benefit from the hibernate.query.in_clause_parameter_padding optimization as well, which allows you to increase the SQL statement caching mechanism.
For more details about loading multiple entities by their identifier, check out this article.

Categories

Resources