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<>();
Related
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
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 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 three tables , a main table MAIN_TB with foreign keys to table TCHARS and TSTATUSES, when i persist MAIN_TB, re-query and display the saved record, the joined columns(tchars from TCHARS and t_status from TSTATUSES) are null but data is saved. what could i be missing?
Table MAIN_TB
#JoinColumn(name = "T_STATUS", referencedColumnName = "T_STATUS")
#ManyToOne
private Tstatuses tStatus;
#JoinColumn(name = "T_CHAR", referencedColumnName = "T_CHAR")
#ManyToOne
private Tchars tChar;
Table TCHARS
#OneToMany(mappedBy = "tChar")
private Collection<MainTb> mainTbCollection;
Table TSTATUSES
#OneToMany(mappedBy = "tStatus")
private Collection<MainTb> mainTbCollection;
Code
public void saveMainTb(){
MainTb mainTb = new MainTb();
Tchars tchars = new Tchars();
Tstatuses tstatuses = new Tstatuses();
tchars.setTChar(new Short("1"));
mainTb.setTChar(tchars);
tstatuses.setTStatus(new Short("1"));
mainTb.setTStatus(tstatuses);
mainTb.setTName("test");
em.persist(mainTb);
}
Result
![Result][1]
Any help would be appreciated
since there is no cascading specified on either owning side or inverse side. I reckon you can try the following code to manually save all entity instances:
em.persist(tchars);
em.persist(tstatuses);
mainTb.setTChar(tchars);
mainTb.setTStatus(tstatuses);
em.persist(mainTb);
You can add the below code to your tStatus and tChar.
fetch=FetchType.EAGER
This will hit the performance, if you don't need to fetch the 2 above objects, always.
Instead, you can add the fetch clause in your query, to fetch the two child objects(tStatus and tChar), along with your mainTb object. This will save you the trouble and fetch all the child objects, you've specified in your fetch clause. Performance optimizer.
I've stack over one small thing:
I have two tables in database, which are related - User and UserReference.
In UserReference i have field userid, which is related to id in User.
i have query to mysql with join left:
"FROM User as user left join fetch user.userReferences WHERE login='"+login+"' or email='"+login+"' AND password = '" +password+ "'" ;
The query is ok and i get results.
I send the results to controller, changing it into list: query.list();
In controller i receive the results and push it into user list.
From the user list i can get info from table User
In table User there is:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
public Set<UserReference> getUserReferences() {
return this.userReferences;
}
And now - i want to get data from table UserReference which are in user list, becouse my query have JOIN.
How can i do it?
i was trying to do something like this:
List<UserReference> userReference = (List<UserReference>) user.get(0).getUserReferences();
System.out.println(userReference.get(0).getAge());
But it doesn't work.
Can you help me ?
You can't cast a Set to a List, iterate over the Set to get userRefences instead.