List of entity inside a hibernate entity with #NamedNativeQueries - java

I have a situation where i get List of entity in hiberate, But inside the main entity i have another entity list. The entities that i use:
EmpListBean.java
#NamedNativeQueries({
#NamedNativeQuery(
name = "EmpList",
//Actual query involves lot of joins
query = " SELECT ID , NAME FROM EMPLOYEE WHERE EMP_ID=:EMPID"
,resultClass = EmpListBean.class
)
})
#Entity
public class EmpListBean {
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String empName;
// This is the list i need to retreive
#ManyToOne
#Column(name="workList")
private List<WorkListBean> workList;
//Getters & Setters
}
WorkListBean.java
#NamedNativeQueries({
#NamedNativeQuery(
name = "WorkListBeanList",
query = " SELECT ID , NAME FROM Work_List WHERE EMP_ID=:EMPID"
,resultClass = WorkListBean.class
)
})
#Entity
public class WorkListBean {
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String workName;
//Getters & Setters
}
The DAO Layer
Query query = session.getNamedQuery("EmpList");
query.setParameter("EMPID", myObj.getEmpId());
List<EmpListBean> oEmpListBean = query.list();
When executing below DAO layer code I get the "workList" Object as empty , I know this can be achieved by iterating the EmpListBean separately and calling named query for WorkListBean separately , but since the data is huge it takes too much time when doing that way, So wanted to know if there is any way that we could fetch WorkListBean inside EmpList Bean. The two entities used here are only for reference , the actual query i use is complex and could not reveal in this forum and it involves lot of table joins, So kindly let me know how this can be possible in hibernate.

I know this can be achieved by iterating the EmpListBean separately and calling named query for WorkListBean separately , but since the data is huge it takes too much time when doing that way
I understand you want to merge the two queries to include the data for both entities, then?
Once the association between EmpListBean and WorkListBean is properly defined, i.e. you have:
class EmpListBean {
...
#OneToMany
#JoinColumn(name = "EMP_ID")
private List<WorkListBean> workList;
}
you should be able to use the following approach:
session.createNativeQuery(
"SELECT employee.* FROM EMPLOYEE employee JOIN Work_List wl JOIN ... WHERE wl.EMP_ID=emp.id AND employee.EMP_ID=:EMPID AND ..." )
.addEntity("employee", EmpListBean.class )
.addJoin( "wl", "employee.workList")
.setResultTransformer( Criteria.ROOT_ENTITY )
.list();
Not sure if it works for named native queries, though, you'll need to check.

As commented by #Javalerner you should use #OneTomany and add Eager Loading with fetch="FetchType.EAGER"
and remove the Column annotation
#OneToMany(fetch = FetchType.EAGER)
private List<WorkListBean> workList;

I like Abd's response if you're looking for a simple #OneToMany.
Rather than forcing the bean to always eager load, you may want to just incorporate this concept into your NamedNative query using the FETCH JOIN style. Using this approach you can craft queries that are EAGER on an object whose association is otherwise Lazy. In that case, Hibernate will only eagerly fetch via your query and generally be lazy, which is I think what most people want.
Feel free to google around. Here is a well written article that may start you off.
http://www.basilv.com/psd/blog/2008/improving-performance-via-eager-fetching-in-hibernate
Best of luck!

to get whatever FetchType.LAZY is, you have to use JOIN FETCH in the sentence. When you use JOIN you get all whatever FetchType.EAGER is but not whatever FetchType.LAZY is
EmpListBean.java
#OneToMany(fetch = FetchType.LAZY, mappedBy="empListBean")
private List<WorkListBean> workList;
WorkListBean.java
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="EMP_ID")
private EmpListBean empListBean;
DAO
String hql = "SELECT empListBean "
+ "FROM EmpListBean empListBean "
+ "JOIN FETCH empListBean.workList workList "
+ "WHERE empListBean.id = :EMPID";
Query q = sessionFactory.getCurrentSession().createQuery(hql);
q.setParameter("EMPID", myObj.getEmpId());
List<EmpListBean> empListBean = query.list();

Related

How to fetch two-level deep #OneToMany association in Spring Data and avoid MultipleBagFetchException

I'm still a JPA beginner and would like to know how to best use Spring powerful features to fetch the following simple structure (associations are lazy by default but I have a use case where the whole structure should be loaded without proxies, possibly with the lowest number of SQL queries generated). Simplified entities concerned:
#Entity
public class Bundle {
#Id
private Long id;
#OneToMany(mappedBy = "bundle", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Group> groups = new ArrayList<>();
}
...
#Entity
public class Group {
#Id
private Long id;
#ManyToOne()
#JoinColumn(name = "BUNDLE_ID")
private Bundle bundle;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Element> elements = new ArrayList<>();
...
public class Element {
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "GROUP_ID")
private Group group;
}
My attempt to find all groups and elements under a given bundle (in order to efficiently process them and convert to a DTO later before returning from an endpoint) was to fetch inside #Query
public interface BundleRepository extends JpaRepository<Bundle, Long> {
#Query("SELECT bundle FROM Bundle bundle "
+ "JOIN FETCH bundle.groups groups "
+ "JOIN FETCH groups.elements "
+ "WHERE bundle.id = :id")
Optional<Bundle> fetchBundle(#Param("id") long id);
}
However, this results in org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags. I did some reading on the subject and found that changing Lists to Sets may do the trick, on the other hand some sources discourage from this.
This double #OneToMany structure seems very ordinary and multiple JOINs also nothing uncommon but nevertherless I'd like to ask you to point out the right approach. Maybe fetching groups for a bundle separately and then for every group fetching its elements? This would be 1 + number of groups queries, not a bit wasteful? Please let me know if considering this as a trade-off in this manner is a step in the good direction.
Assuming that a. duplicates aren't allowed in the collections, and b. you don't care about the iteration order of the entities in the entity object collections for groups and elements (you could still impose ordering when transforming to DTOs using some property of them), then the headline answer is:
Yes, change from List to a Set which communicates both to callers, and to the JPA implementation that duplicates aren't allowed and iteration order can be undefined (which would also solve the bag exception - but at the cost of a gigantic cross-join).
To avoid the gigantic cross join that might result from loading both sets at the same time, do the load of the graph in 2 queries (as long as both participate in the same transaction):
#Query("SELECT bundle FROM Bundle bundle "
+ "JOIN FETCH bundle.groups groups "
+ "WHERE bundle.id = :id")
Optional<Bundle> fetchBundle(#Param("id") long id);
}
and then a second call to populate the elements on the groups:
#Query("SELECT groups FROM Group groups "
+ "JOIN FETCH groups.elements "
+ "WHERE groups.bundle.id = :id")
Collection<Group> fetchBundleGroups(#Param("id") long id);
}
Note that you can in fact ignore the response value of the second call and just do ...
Optional<Bundle> maybeBundle = repository.fetchBundle(id);
repository.fetchBundleGroups(id);
// now, maybeBundle.get().groups.elements will all be populated
due to a useful feature of the JPA spec, which requires that, within a given JPA EntityManager persistence context scope (usually the same as transaction scope), the same object instance is returned for an entity with the same ID, and thus effects from the second query (to populate the elements on the Group objects) will use the same instances returned from the first query.
Worth noting that the same technique also works if there were to be multiple collections on one of the entities - e.g. Bundle.authors - you would just add a 3rd query to populate the authors collection on the Bundle and call that as well to avoid the cross-join between authors and groups.

How to Inner join with #ManyToMany created Table in entity ( Spring Boot)

I use the H2 database and spring boot for this task, I have two entities.
The First entity is:
public class e1{
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "t12",
joinColumns = { #JoinColumn(name = "e1_ID")},
inverseJoinColumns = { #JoinColumn(name = "e2_ID")})
private Set<Game> listSet1 = new HashSet<>();
}
The second entity is:
public class e2{
#ManyToMany(mappedBy = "listSet1")
private Set<Player> listSet2 = new HashSet<>();
}
After running the application we have a table called "t12".
Now, I have a question, I would like to know, How to join with this table I mean ( #JoinTable t12) without an entity from the e2 class.
I mean, when we are running an application Spring use this annotation #JoinTable to create a table for two entities. Now after created this table I want to join with them. but the table ( I mean #JoinTable name "X") has no entity. I want to use Inner join with this table and one of my entities.
Thank you all.
What exactly do you need to do?
Spring Data JPA lets you write native SQL queries using the #Query annotation with nativeQuery = true.
e.g. In your Repository interface, you can try something like:
#Query(
value = "SELECT * FROM e1 INNER JOIN t12 ON t12.e1_ID = e1.id WHERE t12.e2_ID = :e2ID",
nativeQuery=true)
Collection<e1> findAllByE2ID(Long e2ID);
Some useful resources:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#_native_queries
https://www.baeldung.com/spring-data-jpa-query#2-native
https://attacomsian.com/blog/spring-data-jpa-many-to-many-mapping
Please take a look at some naming conventions in java and Spring Boot:
https://www.oracle.com/java/technologies/javase/codeconventions-namingconventions.html
https://www.baeldung.com/java-class-file-naming
https://www.baeldung.com/spring-data-jpa-custom-naming

Hibernate JPA recursive query

I have the following query, I'm using hibernate a JPA provider:
entityManager().createQuery(
"SELECT page FROM ProjectPage page"
+" left join fetch page.categorySet as category"
+ " where page.id = :id "
+ " and category.parentCategory is null "
+ " and (category.status is null or category.status != :status_val) "
,ProjectPage.class).setParameter("id", id).setParameter("status_val", Status.DELETED).getSingleResult();
and below are the entities of ProjectPage and Category respectively:
#Entity
#Table(name="project_page")
#Configurable
public class ProjectPage {
#OneToMany( mappedBy = "parentPage")
private Set<Category> categorySet = new HashSet<Category>();
}
#Configurable
#Table(name="category")
#Entity
public class Category{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parentCategory",fetch=FetchType.EAGER)
private Set<com.se.dataadminbl.model.Category> categorySet = new HashSet<com.se.dataadminbl.model.Category>();
}
in the above query, i'm trying to fetch a ProjectPage along with its categorySet and as shown above class Category contains a set of its type, so every ProjectPage object will contain a set of Category and each object inside this set will contains a set of Category, now the problem is that when i retrieve the ProjectPage object the conditions in the where clause applied only on the first level set of Category not on each set inside each Category, i want to make a recursive query so that i can apply the where condition to the nth level instead of doing that with code, i tried to use interceptor but doesn't work, any idea of how to do that?
The WHERE condition will always filter out nulls when you reference a LEFT JOIN column in your WHERE clause. So the end result is an INNER JOIN.
Neither JPA nor Hibernate support recursive queries, because there's no one and only one standard implementation amongst all databases Hibernate supports.
In case you use PostgreSQL, you can use a Common Table Expression.

JPA / EclipseLink: Joined WHERE query does not give expected result

Now that's very confusing... I have a JPA entity Order that references an entity User. The User can be either buyer or seller of the Order.
Because both buyer and seller can enter additional information for an order, I moved that to an extra entity OrderUserData. There might or might not be a corresponding OrderUserData object, but IF one exists, the user should only be able to see the entry they created (based on USER_ID) and not the one of the other party.
The entities look like this:
#Entity
#Table(name = "T_ORDER")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "SELLER_ID")
private User seller;
#ManyToOne
#JoinColumn(name = "BUYER_ID")
private User buyer;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
private List<OrderUserData> userData = new ArrayList<>();
//..
}
--
#Entity
#Table(name = "T_ORDER_USERDATA")
public class OrderUserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "ORDER_ID")
private Order order;
#ManyToOne
#JoinColumn(name = "USER_ID")
private User user;
private String comment;
//...
}
( User is not very exciting, just ID and basic name fields )
Now when I'm trying to select the appropriate data to display in the website, I have a problem:
String qry = "SELECT o FROM Order o LEFT JOIN o.userData ud "
+ " WHERE (o.seller.id = :userId OR o.buyer.id = :userId)"
+ " AND ( ud.user IS NULL OR ud.user.id = :userId )";
TypedQuery<Order> query = em.createQuery(qry, Order.class);
query.setParameter("userId", userId);
Let's say I execute this, setting userId to 2:
My Database looks like this:
ORDER
=====
ID SELLER_ID BUYER_ID
1 1 2
2 2 3
3 3 1
ORDER_USERDATA
===============
ID ORDER_ID USER_ID COMMENT
1 1 1 Comment that only user 1 should see
2 1 2 Comment that only user 2 should see
But unlike you would expect, when executing the above query, both records are included in the userData list! It seems like JPA is executing two queries (despite the EAGER fetch) and ignoring the WHERE on the second one. Why is that? And what other solution than to loop through the userData list on Java level and kick out the entry that the appropriate user should not see?
There is no way to load OrderUserData objects inside an Order object using a query. Maybe you're confusing the ORM functionality, mapping rows in the database to Java objects, with the query functionality.
Mapping means 1-1 correspondence between rows and objects, hence Order objects always contain all OrderUserData objects for each OrderUserData row related to Order rows.
The fetch type is just a loading strategy, determining at which time are the related objects fetched, as soon as the containing object is loaded (EAGER) or as soon as the contained objects are accessed (LAZY).
You can obtain your list issuing a query on OrderUserData objects with the proper filters and getting Order objects from each of them, i.e.
SELECT ud FROM OrderUserData ud WHERE (ud.order.seller.id = :userId
OR ud.order.buyer.id = :userId) AND ( ud.user IS NULL OR ud.user.id =
:userId )
your query seems to work well as it selects properly Order entity. Then JPA fetch all the OrderUserData child of the selected Order : that's because oneToMany join is not filtered.
I don't think it is possible to modelize pre-filtered oneToMany with eclipseLink (like Hibernate #FILTER), so you should remove it and map orderUserDataId field only. Then you can fetch your entities in 1 query, but they will not be linked
SELECT o, ud FROM Order o, o.userData ud WHERE (o.seller.id = :userId OR o.buyer.id = :userId) AND ( ud.orderUserDataId = o.id and (ud.user IS NULL OR ud.user.id = :userId) )";
On the other hand, if the oneToMany is required by other use cases, then you can create 2 different Order entities :
1 "OrderLight" without the oneToMany
1 "OrderFull" with the oneToMany, derived from OrderLight.
While user3580357 and remigio have already given the correct answer as to why this doesn't work, might I suggest that you create a view on database level.
Something like (might need to be adapted for your needs or RDBMS):
CREATE OR REPLACE VIEW
ORDER_WITH_USERDATA
AS
SELECT o.*, oud.*
FROM ORDER o
LEFT JOIN ORDER_USERDATA oud
ON o.id = oud.order_id
This will essentially give you two different "logical" records for every order. You can then create an additional JPA entity that works on this view and do your SELECT/WHERE... without needing to (LEFT)JOIN at all.

Hibernate join takes too long to work

Here is my problem:
I have this class it has few #oneToMany collections
public class ActivePropertyList implements Serializable
{
#OneToMany
#JoinTable(name = "PropertyAttributeLink",
joinColumns =
#JoinColumn(name = "EANHotelID"),
inverseJoinColumns =
#JoinColumn(name = "AttributeID", referencedColumnName="AttributeID"))
private Collection<AttributeList> attributeList;
#OneToMany(fetch= FetchType.LAZY)
#JoinColumn(name="EANHotelID")
private Collection<Hotelimageslist> hotelimageslist;
#OneToMany(fetch= FetchType.LAZY)
#JoinColumn(name="EANHotelID")
private Collection<Roomtypelist> roomtypelist;
//Getters & Setters ...
When I access this object from XHTML it takes too long to generate as I use <ui:repeat value=#{controller.ActivePropertyList.attributeList}> ...
PropertyAttributeLink has more than 5Mil rows and Images has more than 4Mil rows but when i use simple SQL query innerJoin i takes no more than few ms to generate Lists.
I've tried using namedQuery on AttributeList using HQL query but as AttributeList has no reference to ActivePropertyList as it is unidirectional #oneToMany it throws error on doing so.
Is there a way to create HQL NamedQuery to access each list just once and store it in controller?
something like
public List<AttributeList> getAttributeListByHotelID(int hotelID){
Query q = session().createQuery("from AttributeList AL inner join PropertyAttributeLink PA where PA.hotelID=:hotelID");
return q.list();
}
but this method doesn't work as hql needs AttributeList to know about PropertyAttributeLink
Pointing the joins just make the atributtes available for where conditions and other stuff, you should use FETCH to make the relations eager and have it inmediatly, avoiding the lazy iniciators, something like
from AttributeList AL inner join FETCH PropertyAttributeLink PA where PA.hotelID=:hotelID
As you see isn't so hard, i hope that helps you, you can get more information, as always, in the docs HQL - Associations and joins

Categories

Resources