I have a JPQL like this one:
select distinct d
from Department d
left join fetch d.employees
When I want to fetch one of the lazy property of my Department entity, the distinct is not working any more.
select distinct d, substring(d.htmlDescription, 1,400)
from Department d
left join fetch d.employees
The query returns as much Department as the number of employees in it.
The substring(d.htmlDescription) is important because the property is defined as a CLOB (type TEXT under postgresql):
#Column(columnDefinition = "TEXT")
#Basic(fetch = FetchType.LAZY)
String htmlBody;
The substring function is translated in sql thus limiting the amount of data transfered beetween the database and the web server.
As a workaround, I tried to break the query in two parts :
select d, substring(d.htmlDescription, 1,400)
from Department d where d in (
select distinct d1
from Department d1 left join fetch d1.employees
)
This doestn't work because the JOIN FETCH must not be used in the FROM clause of a subquery.
Finally I found a solution to my problem by :
modifying my mapping
cutting the request in 2 calls.
The htmlBody field is now in another entity. Thus the departement entity is lighter.
class Department{
...
#OneToOne (fetch = FetchType.LAZY,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
Content content = new Content();
...
}
class Content{
...
#Column(columnDefinition = "TEXT")
#Basic(fetch = FetchType.LAZY)
String htmlBody;
...
}
I can then use the following requests :
List<Department> deps = em.get().createQuery(
"select distinct d " +
"from Department d " +
"order by d.id desc ", Department.class)
.setFirstResult(first)
.setMaxResults(count)
.getResultList();
List<Object[]> tuple = em.get().createQuery(
"select d, substring(d.content.htmlBody, 1,400)" +
"from Department d " +
"left join fetch d.employees" +
"where d in (:deps) order by d.id desc")
.setParameter("deps", deps)
.getResultList();
... //Filter the duplicates due to the fetching
That way, I have 2 sql queries. The fetching of employees is done in the second query witch occurs on a small amount of datas. The substring is realized in SQL. Perfect!
Since I cannot make comments, I would like to point out few things that stick out to me as doubtfull.
What is the object returned with distinct d, substring(d.htmlDescription, 1,400)? Could you fetch that String with separate query, or get that substing using Java?
I would trust that that query can be rewritten into one without left join statement.
Maybe you could rewrite the query so you could put substring statement first and then distinct d?
Related
I am running a real life scenario in our database and thus I have no room to alter its structure in case that this comes around as a suggestion. Here is the Structure in question:
EntityA {
#Id
.......
#OneToMany(mappedBy = "xxxx", fetch = FetchType.LAZY)
private Set<EntityB> entityB;
#Column(name = "partition", columnDefinition = "nchar", length = 3)
private String partitionKey;
}
EntityB {
#Id
..........
#Column(name = "partition")
private String partitionKey;
#ManyToOne(fetch = FetchType.LAZY) //EAGER is the default for the to-one
#JoinColumn(name = "bbbb")
private EntityA entityA;
#OneToMany(mappedBy = "EntityCPk.entityB", fetch = FetchType.LAZY)
private Set<EntityC> entityC;
#OneToOne(mappedBy = "cccc", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private EntityD entityD;
}
EntityC {
#EmbeddedId
private EntityCPk entityCPk;
#Embeddable
public static class EntityCPk implements Serializable {
#Column( name = "partition")
private String partitionKey;
#ManyToOne
#JoinColumn(name = "xxxx")
private EntityB entityB;
#ManyToOne
#JoinColumn(name = "xxxx")
private EntityE entityE;
}
}
EntityD {
#id
.........
#MapsId
#OneToOne
#JoinColumn(name = "zzzz", columnDefinition = "nchar")
#PrimaryKeyJoinColumn
private EntityB entityB;
}
EntityE {
#id
.........
#OneToMany(mappedBy = "yyyPk.entityE")
private Set<EntityC> entityC;
}
Now the requirement is to run a query in one go with joins and avoid 1+N scenarios. I assume as far as I have seen that the .LAZY or EAGER annotations are "overwritten" when using the Query annotation within the repository along with the FETCH option. So here is what I have achieved so much (entityXXX and entityYYY do not interfear with our case so I just mention them):
First Attempt with FetchType.LAZY in EntityB.entityC property:
"select a" +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD latest " +
"where a.aProp in ( :props ) " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "
Results, as expected. I get 1 query BUT I get no collection returned in EntityB.entityC due to the lazy annotation and of course it is not present in the query. If I change the the EntityB.entityC to FetchType.EAGER then I get as expected 3 queries. One is the main and N per entityC in Set. So I guess the next step is to join entityC:
Second Attempt:
"select a " +
"from EntityA a " +
"join FETCH a.entityB bs" +
"join FETCH bs.entityXXX as xxx " +
"join FETCH bs.entityYYY as yyy " +
"join FETCH bs.entityD as latest " +
"join bs.entityC as cs " + //please note I am not using FETCH yet
"where a.aProp in ( :props ) " +
"and c.entityCPk.partitionKey = a.partitionKey " +
"and xxx.id = 4 " +
"and yyy.id = 10" +
"and latest.entityB.aProp between :date and :date "
The result is unexpected and I think it has been reported here as well. What I get now is multiples of a(s) all references to the same object equals to the sum of the amount of bs.entityC. So if for example a-1 -> has 1-bs -> has 17 cs and a2 -> has 1-bs -> has 67 cs then I end up with a result set of 84 a objects all the same! This is question one. Why is this happening?
Question 2 is that if I use the FETCH in my new join then I am still not getting my 1 query and now I am not getting exactly multiple instances of A but multiple instances of some kind of Wrappers with a handler property that has references to A makred as EntityA_$$_jvstbc0_4#.
Just to give some insight to the database structure, I am more than sure that this schema started as a many-to-many relationship with a lookup table being EntityB between EntityA and EntityC. I may try to tackle the issue using JoinTable on EntityC joining on partitionKey and id of EntityB while EntityA has the partitionkey and its Id to map on EntityB. However i am not very hopeful of this solution as EntityB has been contaminated with other columns over time which needs to be selected uppon and I am not sure how can I do this.
UPDATE 1: I can see that when join FETCH is used for cs it is augmenting the resultant SQL select with the columns that are necessary i.e. to populate the cs children. Running the query manually I am getting correctly the sum of children as rows. Which makes sense SQL wise but hibernate should have been able to aggregate the additional rows based on their properties. Right enough without the join FETCH I am getting only rows equals to the amount of a. So my second though is that somehow I need to instruct Hibernate to aggregate manually(?)
UPDATE 2: Change of strategy. Instead of starting following an SQL logic, we better have to answer to the following question: Which Class/Entity will give us the granularity we are looking for. In the previous examples we were starting from EntityA trying to limit its children to fit our expected results. However as it has been pointed, this is effectively a corruption of the objects. You cannot "limit" the children cause they all belong to the Entity and fetching a subset of them you run the risk of deleting(?) data. So the approach must be to get the children objects we are interested that point to the parent entities. That we don't alter the data. So here is a query that returns the correct amount of object. No distinct or inexplicable multiplicities:
"select c " +
"from EntityC c " +
"inner join c.EntityCPk.EntityB.EntityD latest " +
"join latest.EntityB.EntityXXX xxx " +
"join latest.EntityB.EntityYYY yyy " +
"join fetch c.EntityCPk.EntityB " +
"where latest.EntityB.EntityA.Id in ( :param ) " +
"and latest.EntityB.aField between :paramA and paramB "
So this seems to answer the issue of the multiplicity of the previous examples as every row is based on the "finer" child object that resolves its parent via the -ToOne relationship. Also there are no more dangerous aliases in the join fetch. There is only one more issue. It introduces a 1+N query problem for EntityB that I cannot get rid off.
We are using the Hibernate #NamedQuery annotation on an Entity and are using join fetches to eagerly retrieve the data we want at query time. We have this working for the most part, but the FetchMode.SUBSELECT that we have on a relationship does not appear to be working. Instead, it appears that the query generated is still doing a single query with the join fetch, rather than the expected 2nd query.
Again, we're using Hibernate's #NamedQuery and not the JPA style.
The reason that we want to use the SUBSELECT for our case is that we have multiple #OneToMany relationships and we want to avoid massive perf issues with the Cartisean Product
Here's the setup we have:
#Entity
#NamedQueries(
#NamedQuery(
name = "StackOverflowNamedQuery",
query = "SELECT distinct f FROM Foo f "
+ "LEFT JOIN FETCH f.fooOneToMany bar " // This is a straight JOIN FETCH
+ "LEFT JOIN FETCH bar.barOneToMany " // WANT THIS TO DO A SUBSELECT
+ "LEFT JOIN FETCH f.manyToOne_boo "
+ "LEFT JOIN FETCH f.manyToOne_blah "
+ "WHERE f.id = :id "
+ "f.isRemoved = false"
)
)
Then over on Bar.getBarOneToMany(), here's the annotation setup we have for that OneToMany relationship
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "entity_id", referencedColumnName = "id")
#Fetch(FetchMode.SUBSELECT)
public Set<BarOneToMany> getBarOneToMany() {
return barOneToMany;
}
So basically, we want to Fetch one of the OneToMany with a join fetch, and then we want the other one to be done by a SUBSELECT. Is this possible like this?
Thanks.
I have 3 classes, I am trying to get a list of all the events of an eventhost that a user is subscribed to. I am probably thinking way too complicated but I have very little experience with JPA/HQL.
User class
#ManyToMany
#JoinTable(name = "Subscriptions", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id") , inverseJoinColumns = #JoinColumn(name = "event_host_id", referencedColumnName = "id") )
private List<EventHost> subscriptions;
EventHost class
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "event_host_id", referencedColumnName = "id", updatable = true)
private List<Event> events;
I tried using this query, but it tells me that subscriptions is not mapped, which it is not since it's not a java class.
String hql = "SELECT o FROM Event WHERE event_host_id IN (SELECT a FROM EventHost WHERE id IN(SELECT b FROM User WHERE = + " + userid + "))";
I know injecting the userid like this is bad practice, I'm just doing it for testing purposes.
Please ask if you need something more, I would really like to understand how to write a query for this.
This question should really be HQL with two join tables, but I'll let you change it. Since its HQL, or JPA, it's database independent.
Anyway, any time you see a OneToMany or ManyToMany relationship you have a join table and so you should be thinking joins. It's always a good idea to look at the sql create table statements to see what's going on. In this case your user_subscriptions join table is:
create table user_subscriptions (user_id integer not null, subscriptions_id integer not null)
and your event_host_events join table is this:
create table event_host_events (event_host_id integer not null, events_id integer not null)
Nothing new there. When you're trying to get something new working that you don't intuitively understand, break it down into things you can do. For example, you can execute two queries, getting a Users subscriptions first, and then getting the Events for those subscriptions:
Query query = session.createQuery("select u.subscriptions from User u where name = :name");
query.setParameter("name", name);
List<EventHost> subscriptions = query.list();
List<Event> events = new ArrayList<Event>();
Query query2 = session.createQuery("select s.events from EventHost s where id = :id");
for (EventHost s: subscriptions ) {
query2.setParameter("id", s.getId());
events.addAll( query2.list());
}
Not elegant, but it works. Then, keeping join in mind, figure out how to make one statement out of the two of them.
Query query = session.createQuery("select s.events from User u join u.subscriptions s where u.name = :name)");
query.setParameter("name", name);
return query.list();
The join will use an inner join by default, so you're ok there. The JPA provider will auto-magically join your three Entity tables and two Join Tables for you:
select
event4_.id as id1_2_
from user user0_
inner join user_subscriptions subscripti1_ on user0_.id=subscripti1_.user_id
inner join event_host eventhost2_ on subscripti1_.subscriptions_id=eventhost2_.id
inner join event_host_events events3_ on eventhost2_.id=events3_.event_host_id
inner join event event4_ on events3_.events_id=event4_.id
where user0_.name=?
Aren't you glad you don't have to write that query?
Hibernate / Java newbie here, any help will be greatly appreciated!
So...... I have a table called ITEMS and a ITEM_OWNER_JOIN table joined by the
"itemKey" column and the "owners" column which is a Set of String values...
In Item.java I have:
#ForeignKey(name="FK_ITEM_OWNER_FK")
#ElementCollection(targetClass=java.lang.String.class, fetch = FetchType.Eager)
#JoinTable(name= "ITEM_OWNER_JOIN", joinColumns=#JoinColumn(name="itemKey"))
private Set<String> owners = new HashSet<String>();
and basically I'm trying to run a HQL querying for results where the owners match a
searchText param....
so I've tried:
Query q = session.createQuery("select distinct i.itemKey from Item i inner join"+
" i.owners o where o.owners like '"+searchText+"'");
and I am getting a org.hibernate.QueryException: cannot dereference scalar collection element: owners [select distinct w.workspaceKey from.....]
I've tried researching for that exception to no avail... :(
Thank you for your time!
Something as below
HQL
select i
from Item i
inner join i.owners io
where io like 'searchText';
Oracle Query
SELECT Distinct(i.itemKey)
FROM Item i, ITEM_OWNER_JOIN io
WHERE i.itemKey = io.itemKey and io.x like '%%';
where 'x' is column name.
Working example from my application
From entity:
#ElementCollection
#JoinTable(name = "rule_tagged_name", joinColumns = #JoinColumn(name = "re_rule", referencedColumnName = "id"))
private List<String> ruleTagNames;
DB Columns
RE_RULE NUMBER
RULE_TAG_NAMES
HQL
Select ru FROM Rule ru inner join ru.ruleTagNames rt_name WHERE rt_name in :tagNameList
Try using with IN operator as owners is multiple.
Query hqlQuery = session.createQuery("select distinct i.itemKey from Item i inner join"+
" i.owners o where o.owners in :ownersParam");
Then set parameter owners with the owner set value,
Set<String> ownerSet = new HashSet<String>();
ownerSet.add(searchText);
hqlQuery.setParameterList("ownersParam", ownerSet);
//then retrieve result
I have a mapped entity with a property "latestHistory", which is mapped through a join table, like:
class Record {
#OneToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.LAZY, optional = true)
#JoinTable(name = "latest_history_join_view", joinColumns = { #JoinColumn(name = "record_id") }, inverseJoinColumns = { #JoinColumn(name = "history_id") })
#AccessType("field")
public History getLatestHistory() { ... }
}
The mapping works correctly when I call myRecord.getLatestHistory().
I have a complex native SQL query, which returns a batch of Records, and joins on the History for each record using the join table. I want to return Record entites from the query, and have the History objects populated in the result. My attempt looks like this:
StringBuffer sb = new StringBuffer();
sb.append("select {r.*}, {latestHistory.*}");
sb.append(" from record r");
sb.append(" left join latest_history_join_view lh on lh.record_id = r.record_id");
sb.append(" left join history latestHistory on latestHistory.history_id = lh.history_id");
SQLQuery query = session.createSQLQuery(sb.toString());
query.addEntity("r", Record.class).addJoin("latestHistory", "r.latestHistory");
When I do this, it generates a query like:
select
r.record_id, r.name...,
r_1.history_id, --this part is wrong; there is no such alias r_1
latestHistory.history_id, latestHistory.update_date, ...
from record r
left join latest_history_join_view lh on lh.record_id = r.record_id
left join history latestHistory on latestHistory.history_id = lh.history_id
How can I get it to join correctly and fetch my association, without messing up the select list?
[Update: some of the approaches that I've tried:
select {r.*}, {latestHistory.*} -> SQL error, generates a wrong column name "r_1.history_id"
select {r.*}, {anyOtherEntityAssociatedToR.*} -> wrong column name (as above)
select {r.*}, {r.history_id}, {latestHistory.*} -> hibernate error, r has no history_id column
select r.*, lh.history_id as history_id -> this works (though hackish), but doesn't accomplish the join
select r.*, lh.history_id as history_id, latestHistory.* -> appears correct, but results in column name collisions
select r.*, {latestHistory.*} -> error when hibernate looks for a nonexistent column in the result set (this happens if there is any alias at all in the select list)
It doesn't seem to make a lot of difference whether I use addEntity(...) or addJoin(...), as long as the leftmost (root) entity is added using addEntity.
]
I'm thinking you actually need to specify full path for your latestHistory in select e.g.
select {r.*}, {r.latestHistory.*}
otherwise Hibernate gets confused and attempts to treat it as a separate entity. The other option is to not specify injected aliases in select at all which should work for a single "to-one" relationship so long as column order in your tables matches property order in your entities.
I've never tried this on #OneToOne over association table, though.