Hibernate query by example (from Spring 3) - java

I've made my entity classes Adress, Road and County. A Road is in a County and an Adress in on a Road. I would like to list all the Adresses in a County. Therefore, in my AdressService I say:
public List<Adress> AllAdresses(County county) {
Adress adress = new Adress();
Road road = new Road();
road.setCounty(county);
adress.setRoad(road);
Example example = Example.create(adress);
return (List<Adress>) adressDAO.query(Adress.class, example);
}
In my AdressDAO I have query():
public List query(Class<?> c, Example example) {
return getSession().createCriteria(c).add(example).setMaxResults(100).list();
}
This executes the following query on my database server:
select this_.AdressId as AdressId2_0_,
this_.Description as Descript3_2_0_,
this_.DescriptionShort as Descript4_2_0_,
this_.HouseLetter as HouseLetter2_0_,
this_.HouseNr as HouseNr2_0_,
this_.RoadId as RoadId2_0_
from tblAdress this_
where (this_.HouseNr=0)
limit 100
I had expected it to at least include SOME information about my entity County, and an inner join with tblRoad. tblRoad has a primary key roadId, so I expected this_.roadId to be joined with tblRoad.roadId, and I expected tblRoad.countyId to be set to the primary key of County, that is countyId.
Why is the query in this example not built correctly when I use my own entity types? If I only use integers and strings, they work fine, but not entities. How do I make joins like this work with my own entities?

From the Hibernate docs:
Version properties, identifiers and
associations are ignored
And that, as they say, is that.

Related

Convert Integer to String in Spring Data JPA Query

I need to optimize a query that iterates over several objects and I wanted Spring Data to let the database handle it. I want to end up with a HashMap<String,String> that looks like
2134_9877, 9877
2134_2344, 2344
3298_9437, 9437
The SQL would be select convert(varchar,b.id)+'_'+convert(varchar,a.id)',a.id from t1 a join t2 b on a.jc = b.jc
So far, I've got Whatever-QL in the repository that looks like:
#Query("SELECT new map (a.bkey, a.akey) FROM mergeTable a WHERE a.discr= ?1")
The problem is, bkey is not unique, it is only unique when paired with akey and the monstrosity that I have to feed it to wants them combined with an underscore: 2345_2177.
I have tried a.bkey.toString and ''+a.bkey and new String(a.bkey) and just string(a.bkey) (that last gives a new exception but still doesn't work) but Spring doesn't like any of these. I can find no questions asking this and it seems I cannot use SQLServer's convert() function as this ain't SQL.
How can I concatenate the Integers as Strings with an underscore in this #Query?
PS: Using the native query that's been debugged in SQLServer throws some weird alias exception in Hibernate so I think 'going native' is predetermined to be a dead end.
If I have understood it right, the 'Whatever-QL' is called JPQL, and the operator CONCAT can be used. Only the use of it, as it accepts two or more parameters depends on the JPA version you are running.
Here is the answer.
JPA concat operator
You could add a getter to your entity like this:
public String getCombinedKey(){
return a.akey + "_" + a.bkey;
}
The advantage is you could handle here null's and other things if you want and it's more reusable in case you need this in another place. If you do it just in the repository you will have to copy it everytime.
Your query would then be:
#Query("SELECT new map (a.combinedKey, a.akey) FROM mergeTable a WHERE a.discr= ?1")

Object modification inconsistency on Play framework 2.2.X

Here's the deal:
public static List<Survey> getFilteredSurveys(Municipality municipality, Company company) {
String sql = "SELECT DISTINCT id FROM survey INNER JOIN " +
"(SELECT SURVEY_ID FROM publicity INNER JOIN brand "+
"ON publicity.brand_id=brand.id WHERE brand.company_id="+company.getId()+") "+
"ON survey_id=survey.id WHERE survey.municipality_id="+municipality.getId();
RawSql rawSql = RawSqlBuilder.parse(sql).create();
List<Survey> surveys = Ebean.find(Survey.class).setRawSql(rawSql).findList();
for (Survey survey : surveys) {
List<Publicity> publicities = new ArrayList<>();
for (Publicity publicity : survey.publicities) {
if(publicity.getBrand().getCompany() == company){
publicities.add(publicity);
}
}
survey.setPublicities(publicities);
}
return surveys;
}
This app is meant for measuring Publicities in a given place,
So people upload a 'Survey' of a place containing all the 'Publicity' that place has.
That function is supposed to return a List,
Each Survey has a List,
And each Publicity has a Brand, which is associated to a Company (ex. Coke -> Coca Cola Co.)
What I'm trying to do is this:
Given a Company, show all the surveys that contain a 'Coca Cola Co.' publicity, but showing only the publicities that belong to 'Coca Cola Co.'
I have a 'Surveys' controller which receives a form with a Municipality and a Company, calls this method, and it renders a view with its result.
This is part of the view template:
#(surveys: java.util.List[Survey])
#for(survey <- surveys){
#for(publicity <- survey.getPublicities){
<tr>
<td>#publicity.getBrand.getName</td>
<td>#publicity.getType.getName</td>
<td>#publicity.getSquareMeters</td>
</tr>
}
}
Problem: even though I removed some publicities from each Survey, all the publicities show up in the view. Why is this happening?
I know I'm not persisting the changes, and I don't want to, I just want to temporarily obfuscate the data so the user only sees the publicities that belong to a given company.
Why isn't this view using the surveys as they are given to it, modified?
Actually I'll put this in an answer ...
You should look at the SQL executed in the log (because I suspect you are getting N+1) here and you could fairly easily avoid that.
You should probably look to change your raw sql to include the publicities columns in the select clause (name, type, squareMeters) to avoid the extra queries.
Alternatively you could add fetch("publicities") to the query (so that they are fetched eagerly via a query join 100 at a time).
Also refer to:
https://github.com/ebean-orm/avaje-ebeanorm/issues/223
... RawSql that includes a OneToMany not working
https://github.com/ebean-orm/avaje-ebeanorm/issues/224
... Enhancement adding RawSqlBuilder.tableAliasMapping()
Ideally you'd be able to use 4.5.2 and take advantage of that fix and that enhancement.
So, I found a fix,
My fix was:
for (Survey survey : surveys) {
survey.getAddress(); //This line fixes the issue
List<Publicity> publicities = new ArrayList<>();
for (Publicity publicity : survey.publicities) {
if(publicity.getBrand().getCompany() != null){
if(publicity.getBrand().getCompany().getId().equals(company.getId())){
publicities.add(publicity);
}
}
}
survey.setPublicities(publicities);
}
My guess is that the problem resides in the way ebean lazily instantiates objects, despite setting Publicities to FetchType.EAGER, and the fact that the output from this function was the expected one, also inspecting surveys in the controller seemed to be ok, and also a #println(surveys) in the view showed only the publicities corresponding to the company I had selected.

Hibernate Criteria API - How to get a list of an object attribute

I have two classes
class House {
String id
Long size
Resident resident
}
class Resident {
String id
String name
}
What I want, is a list of Residents but i need some restrictions for the House. E.q. on the size e.q. > 20 and limit results by 10
The Residents have no references to the House.
I really don't know how to go on with this.
I tried this:
Criteria crit = session.createCriteria(House.class);
crit.add(Expression.ge("size", 20));
crit.setMaxResults(10);
crit.addOrder(Order.desc("size"));
return crit.list();
but i dont know how to get the connection to the Resident
Try following code:
Criteria crit = session.createCriteria(House.class);
crit.add(Expression.ge("size", 20));
crit.setMaxResults(10);
crit.addOrder(Order.desc("size"));
crit.setFetchMode("resident", FetchMode.JOIN);
crit.setProjection(Projections.property("resident"));
return crit.list();
"The Residents have no references to the House" - AFAIK Hibernate Criteria can't create restrictions on another entry from a given entry when there is no association.
So you can't create a Criteria for Resident.class and add Restrictions there.
What you could try is to create a Criteria for your House.class, add the required Restrictions, and then set a Projection to get out your Resident.class instances.
What have you tried so far?

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.

Association of 4 tables in hibernate

I have 4 tables involved in this query.
Campaign - many to one business
Business - one to many client
Client - one to one contact
Contact
In contact there is the field contact_name which is unique. I need to retrieve all campaigns related to contact(via client and business) which campaign field type equals 2.
What is the best way to do it with hibernate?
In SQL is will look like this:
select *
from campaign,contact, business, client
where campaign.type=2
and client.contact_id = contact.contact_id
and contact.name = 'Josh'
and client.business_id = business.business_id
and campaign.campaign_id = business.business_id
I think that the following should work.
from Compaign where Compaign.type=2 and compaign.business.client.contact.contact_name=:name
You can execute native SQL Queries too using createSQLQuery() method of Session.
You can also use Scalar Property to avoid the overhead of using ResultSetMetadata.
You can find more information on this from here

Categories

Resources