The PrimeFaces TabView
component supports a flag attribute named dynamic (defaults to false). When this attribute is set to true, PrimeFaces will load only the content of the initial opened tab, while the rest of content is loaded via AJAX only when the user clicks on tabs.
So, let's image that we have the following simple database with two
tables:
Now, for each category, we want to create a tab, and each tab to be
loaded dynamically. We can write something like below:
<h:form
id="form">
<p:tabView dynamic="true"
cache="true">
<p:tab title="My Site">
Check out our categories
</p:tab>
<c:forEach
items="#{dataBean.allCategories}" var="t">
<p:tab title="#{t.name}">
<p:dataList
value="#{t.productsCollection}" var="q"
type="ordered">
<f:facet name="header">
Products
</f:facet>
#{q.product}
</p:dataList>
</p:tab>
</c:forEach>
</p:tabView>
</h:form>
Ok, so the first tab is just a dummy tab that doesn't contain data from
the database. While the rest of tabs reflect the names and number of available
categories. Notice that we have set dynamic="true", and moreover we have set cache="true".
This means that caching only retrieves the tab contents
once and subsequent toggles of a cached tab does not communicate with
server.
Now, let's take a quick look to our code used for querying database.
We can use Hibernate JPA, and the
relevant part is:
@OneToMany(cascade
= CascadeType.ALL, mappedBy = "categories", fetch = FetchType.EAGER)
private
Set<Products> productsCollection;
Notice that Hibernate is by default lazy, so we manually turned
fetching to eager.
Now, if we run the application we see the following output:
Notice that our tab is not even containing data from database. Is just
a dummy tab with some intros. But, since the rest of tab's names are actually
our categories names, the application must hit the database. If we check the
log we notice that even if load only the names of the categories, Hibernate
will load the products from each category also. Obviously, this is the effect
of eager loading.
Moreover, when we click on a tab of a category, this is happening again
until all tabs are cached. Now, we may want to switch to the default Hibernate
fetching style, which is LAZY
(simply delete the fetch
element, or manually set it):
@OneToMany(cascade
= CascadeType.ALL, mappedBy = "categories", fetch = FetchType.LAZY)
private
Set<Products> productsCollection;
When the application starts, everything looks fine. We query only the
categories names. But, when we click on other tab, we get an error of type: org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
javaee.entites.Categories.productsCollection. Well, this is a well-known
Hibernate exception caused by the fact that Hibernate cannot hit the database
to initialize the proxy objects since the connection is already closed.
There are a few approaches to solve this issue, like:
Use
PersistenceContextType.EXTENDED with @Stateful EJB - This works only in JEE and the
container will manage the database transactions. But, you have to pay attention
to the number of stateful beans because they may cause memory issues. The N+1 issue is also a risk.
Load
collection by join - for
example, in our case we can go for a left join fetch of type: SELECT c FROM Categories c
LEFT JOIN FETCH c.productsCollection. But, this needs a new query to access
each model class collection (lazy) attribute. Against this disadvantage, we
have some advantages like the fact that we fire a single query in the database
and will bring only the desired data. Moreover, the N+1 issue is not a risk.
Use the @NamedEntityGraph - provides a query independent way to define a graph of entities which will be fetched with the query.
Use the @NamedEntityGraph - provides a query independent way to define a graph of entities which will be fetched with the query.
EclipseLink
- This is an alternative to Hibernate JPA that works via a LAZY strategy (hint)
that can be set for the persistent provider. This way the persistent provider
will know that you expect those data lazy. Moreover, the N+1 issue is not a risk.
Load
collection by Open Session in View - This is basically a solution
that relies on keeping a database connection opened until the end of the user
request. The implementation is based on a filter that handles the transactions.
The N+1 is also a risk, and there are no really advantages of this approach.
We can chose here the first approach. So, we wrote a Stateful bean as
below:
@Stateful
public class
CategoriesAndProducts {
@PersistenceContext(unitName =
"javaee_PrimeFacesHibernateLazy_war_1.0PU", type=PersistenceContextType.EXTENDED)
private EntityManager em;
public List<Categories>
getCategoriesAction() {
TypedQuery<Categories> query =
em.createNamedQuery("Categories.findAll", Categories.class);
List<Categories> results =
query.getResultList();
return results;
}
}
Now, when we click on a tab, Hibernate will query only the products
from the selected category. You can try yourself an implementation via left
join also.
The complete example is available here.
You can easily run it with MySQL 5 and WildFly 10.
Niciun comentariu :
Trimiteți un comentariu