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.
Related
I have an entity with #OneToMany relationship to a list of second entity.
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "PAYLD_ID")
#Fetch(FetchMode.SUBSELECT)
private List<KeyValueEntity> kvList;
So the subquery is done separately. My issue is how can I run my own query on subselect as I need to resolve some crossreferences?
I'd like to be able to, whenever the subquery gets executed for List to provide my own native or named query.
Can that be done in Hibernate (using version 4.2.21)?
Main entity is using native query:
Payload {
long id;
string cd
#OneToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
List<KeyValueEntity> kvList;
}
Named native query (short for brevity):
select pl.id, sct.cd from payload pl join SCT sct on sct.CSN = pl.TCSN
When the subquery gets executed to retrieve the kvList via the #OneToMany relationship, I see this query:
select kv.pl_id, kv.keycode, kv.value from KV kv where kv.pl_id=?
However, I'd like to change the subquery (the above) to :
select kv.pl_id, sct.CD, kv.value from KV kv join SCT sct on kv.keycode=sct.CDN where kv.pl_id=?
Is there a way to do that?
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?
I have built a list of taggable documents, with a many-to-many relationship between the tags and the documents. I would now like to use the hibernate criteria mechanism to query a "summary" of each tag, which includes a count of how often a particular tag has been used, with an additional restriction on whether or not the document has been published.
The entities I'm using roughly look like this (You'll note an SQL join table in the middle there):
#Entity
public class DocumentTag {
... various things ...
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "tags")
private List<Document> documents = new ArrayList<>();
}
#Entity
public class Document {
... various things ...
#Basic
#Column(name = "published", columnDefinition = "BIT", length = 1)
protected boolean published = false;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "document_tag_joins",
uniqueConstraints = #UniqueConstraint(
columnNames = {"document", "tag"}
),
joinColumns = {#JoinColumn(name = "document")},
inverseJoinColumns = {#JoinColumn(name = "tag")})
private List<DocumentTag> tags = new ArrayList<>();
}
Given the above, I've managed to figure out that building the query should work more or less as follows:
Criteria c = session.createCriteria(DocumentTag.class);
c.createAlias("documents", "docs",
JoinType.LEFT_OUTER_JOIN,
Restrictions.eq("published", true)
);
c.setProjection(
Projections.projectionList()
.add(Projections.alias(Projections.groupProperty("id"), "id"))
.add(Projections.alias(Projections.property("createdDate"), "createdDate"))
.add(Projections.alias(Projections.property("modifiedDate"), "modifiedDate"))
.add(Projections.alias(Projections.property("name"), "name"))
.add(Projections.countDistinct("docs.id"), "documentCount"));
// Custom response entity mapping
c.setResultTransformer(
Transformers.aliasToBean(DocumentTagSummary.class)
);
List<DocumentTagSummary> results = c.list();
Given the above, the hibernate generated SQL query looks as follows:
SELECT
this_.id AS y0_,
this_.createdDate AS y1_,
this_.modifiedDate AS y2_,
this_.name AS y3_,
count(DISTINCT doc1_.id) AS y5_
FROM tags this_
LEFT OUTER JOIN tag_joins documents3_
ON this_.id = documents3_.tag AND (doc1_.published = ?)
LEFT OUTER JOIN documents doc1_
ON documents3_.document = doc1_.id AND (doc1_.published = ?)
GROUP BY this_.id
As you can see above, the publishing constraint is applied to both of the left outer joins. I'm not certain whether that is by design, however what I need is for the published constraint to be applied ONLY to the second left outer join.
Any ideas?
I was able to circumvent this problem by coming at it sideways. First, I had to change the "published" column to use an integer rather than a bit. Then I was able to slightly modify the projection of the result as follows:
// Start building the projections
ProjectionList projections =
Projections.projectionList()
.add(Projections.alias(
Projections.groupProperty("id"), "id"))
.add(Projections.alias(
Projections.property("createdDate"),
"createdDate"))
.add(Projections.alias(
Projections.property("modifiedDate"),
"modifiedDate"))
.add(Projections.alias(
Projections.property("name"), "name"));
if (isAdmin()) {
// Give the raw count.
projections.add(Projections.countDistinct("docs.id"), "documentCount");
} else {
// Use the sum of the "published" field.
projections.add(Projections.sum("docs.published"), "documentCount");
}
I acknowledge that this doesn't actually answer the question about why hibernate criteria constraints on many-to-many tables get applied to all tables, but it solved my problem.
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 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?