I need to select products based on tags, here are the tables
products: productId, name, description, price, etc
tags: tagId, name
product_tags: productId, tagId
and I have 2 classes Product and Tag and relation is specified in Product class
#OneToMany(cascade = CascadeType.DETACH)
#JoinTable(
name = "product_tags",
joinColumns = #JoinColumn(name = "productId"),
inverseJoinColumns = #JoinColumn(name = "tagId")
)
public List getTags() {
return tags;
}
public void setTags(List tags) {
this.tags = tags;
}
Please note I only want to select products and not tags. following works fine
Criteria cri = getSession().createCriteria(Product.class);
cri.setFirstResult(index);
cri.setMaxResults(limit);
return cri.list();
As I am trying to get list for pagination, so I need total number of pages that can be retrieved by getting totalRecords/recordPerPage
Criteria cri = getSession().createCriteria(Product.class);
//add any required filter to criteria
//e.g cri.add(Restrictions.like("name", keyword, MatchMode.ANYWHERE));
//********** Following code is in utility function ******************/
//Get total number of records matching criteria
cri.setProjection(Projections.rowCount());
Long totalRecords = (Long)cri.uniqueResult();
//Get paginated records
cri.setProjection(null);// This is evil but works
cri.setFirstResult(index);
cri.setMaxResults(limit);
paginatedRecords = cri.list();
Question 1: Is it possible to set some thing like cri.setProjection(Product.class) instead of setting it null, I am aware that I can create a projection list and add all the column of product class but that seems overkill + the common part is in utility function and I found no way to retrieve the previous projection. cri.getProject()
Why I need another method because cri.setProjection(null) fails when I apply join, because it will project all the column of products, tags, product_tags. which cannot be casted to List
Get all products that have associated tag ids as (4,5,6)
cri.createAlias("tags", "t");
cri.add(Restrictions.in("t.tagId", new Integer[]{4,5,6}));
Here is the issued query
select
this_.productId as productId1_9_1_,
this_.categoryId as category6_9_1_,
this_.description as descript8_9_1_,
this_.name as name13_9_1_,
tags3_.productId as productId1_9_,
t1_.tagId as tagId2_10_,
t1_.tagId as tagId1_11_0_,
t1_.name as name4_11_0_,
from
products this_
inner join
product_tags tags3_
on this_.productId=tags3_.productId
inner join
tags t1_
on tags3_.tagId=t1_.tagId
where t1_.tagId in (4,5,6) limit 25
I have found a work-around for this as follows
cri.setProjection(null)
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
This will fix type cast exception.
Question 2: The back-end query is still the same, it join and project all the columns of all involved tables. ((Yakkh dirty)), Why the same query?, I am expecting projections on Product class only
Related
I have a class ReleaseNote saved in the table RELEASE_NOTES and a table RELEASE_NOTE_USER_READ with the columns RELEASE_NOTE_ID and USER_ID where I want to save which users have already read a releaseNote, so I don't show it again.
Since we are using a microservice architekture I don't have a user object in that microservice and don't want to add one. Although it won't be a problem to add a field usersRead to the ReleaseNote which contains the userId of all the users who have read the releaseNote.
I need to perform two actions:
Pass a list of releaseNoteIds to be saved in the table for a given userId, so I can keep track of the releaseNotes a user has read.
Select all releaseNotes a user has not read. The SQL-Statement for this would look like:
SELECT * FROM RELEASE_NOTES WHERE ID NOT IN (SELECT RELEASE_NOTE_ID FROM RELEASE_NOTE_USER_READ WHERE USER_ID = :userId)
How can I achieve this using the EntityManager or Hibernate specific functionality?
Found the solution myself. Add field Set usersRead to ReleaseNote and annotate like the following:
#ElementCollection
#CollectionTable(name = "RELEASE_NOTES_USER_READ",
joinColumns = #JoinColumn(name = "RELEASE_NOTE_ID"))
#Column(name = "USER_ID", table = "RELEASE_NOTES_USER_READ")
private Set<Integer> usersRead = new HashSet<>();
I have performance problems with the group by feature in Hibernate. Consider this 2 classes:
public class Project
{
...
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="user")
private User user;
...
}
public class User
{
...
private Integer id;
private String name;
...
}
Now I try to get a list of all Users assigned to the project. But this query is uselessly slow (more than 100'000 project entries, a lot of joins):
Session session = this.sessionFactory.openSession();
String SQL="SELECT user.id as id, user.name as name FROM Project p GROUP BY p.user.id";
Query q = session.createQuery(SQL);
q.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List<Object> list = q.list();
session.close();
I try to change the query this way, but this is not working either because the variable user is an Object (but this would work as a native SQL query):
SELECT id, name FROM User WHERE id IN(SELECT user FROM Project GROUP BY user)
Any other ideas? Thank you in advance!
Try creating an index from the foreign key column.
#javax.persistence.Table(name = "project")
#Table(appliesTo = "project" ,indexes = #Index(columnNames = "user", name = "user_index"))
#Entity
public class Project
{
..
Update
columnNames has been depricated. Use columnList instead.
#Index(columnList = "user", name = "user_index")
Hope this helps.
I doubt this has to do with Hibernate being slow. Most probably the SQL query is slow if run directly on the database as well.
One good practice is to create indices whenever you have a foreign key in your table. In your case create an index for user_id on your project table and run the query once more.
If you want to get all users assigned to the project you don't need group by. Do something like this
select user from Project project inner join project.user user where project.id = :projectId
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 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.