How to design JPA polymorphic relationships in java? - java

I am designing a product catalogue. I would like to have a category tree, where products can be connected only to LeafCategories, that can have one parent Category.
I am using Spring Boot, Spring Data and Hibernate 4 and H2 database(for now).
Base entity for the task is AbstractCategory (Is it there a better way to inherit relationships ?) (Getters and Setters omitted, NamedEntity is a #MappedSuperclass with String name and Long id)
public abstract class AbstractCategory extends NamedEntity{
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "parentId")
Category parent;
}
Category entities - they are not leafs and cannot have Products connected to them:
#Entity
public class Category extends AbstractCategory {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
Collection<AbstractCategory> subcategories;
}
LeafCategory it can be used as a property for my Product entity.
#Entity
public class LeafCategory extends AbstractCategory {
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "category")
Collection<Product> products;
}
I have a very simple CrudRepository for Category and an identical for LeafCategory
#Repository
#Transactional
public interface CategoryRepository extends CrudRepository<Category, Long> {}
When I load a Category from CategoryRepository and access getSubcategories() I get following exception:
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: uj.jg.domain.products.Category.subcategories, could not initialize proxy - no Session
First of all - How can I improve the design? Second and more concrete question is why is #Transactional not keeping a session open? I know I could just use FetchType.EAGER, but it's a recursive structure - if my understanding of Hibernate is correct it would mean loading the whole subtree, and I don't want that. I don't want to use Hibernate.initialize either.
I do not have any config for database or hibernate. I am using devtools from spring.boot:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>

How can I improve the design?
It looks reasonable to me.
Why is #Transactional not keeping a session open?
You have placed #Transactional on the repository. The DB session is open only for the time of running the query, which returns the categories with their subcategories marked as lazy-loaded. Then, the session is closed (once the repository method returns) and you are trying to access the subcategories after that, when there's no session any more. Move the #Transactional annotation higher up the call stack - to the service layer if you are using a 3-layer architecture (cf. this post).
Since the repository methods run just a single query, there's no need to mark them as #Transactional - they will run within a transaction anyway. It only makes sense to have #Transactional when you run several queries or run a query and some other processing (which could throw an exception and you'd want the query to be rolled back because of it). That's why, again, if you want to explicitly mark anything as #Transactional it would rather be in the service layer.

First of all, you get the LazyInitializationException because the Session got closed and not all children were initialized.
Even if you used EAGER (which is often a bad decision), you'd only fetch a single level in your nested children tree.
You can use recursion to traverse all children and force their initialization before returning the result from the DAO method, which requires you to provide a custom implementation for the find method:
public Category findOne(Long id) {
Category category = entityManager.find(Category.class, id);
fetchChildren(category);
return category;
}
public void fetchChildren(Category category) {
for (Category _category : category.getSubcategories()) {
fetchChildren(_category);
}
}

Related

OneToMany lazy initialization when needing collection data

What's a workaround if I have a relation OneToMany and would like to access the collection that is lazy loaded? Currently I get LazyInitializationException having this:
Club entity:
#Entity
public class Club {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "club", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JsonBackReference
private List<Player> players;
Player entity:
Is it a good idea to have two methods, where one fetches data without players and the second one that fetches also players?
#Override
List<Club> findAll();
#Query("Select clubs from Club clubs left join fetch clubs.players")
List<Club> getAllClubsWithPlayers();
What I'm thinking of is that it is a bad idea, because if I have a situation where I have for example 4 properties that are lazy loaded and I'd need at once 3 of them, I'd have to have a query like: getAllClubsWithPlayersAndSponsorsAndCoaches, so I'd have to have a lot of combinations of such queries.
I don't want to use EAGER, so could you tell me if it's a common way to do this if I need access to players from the Club sometimes which is undoable with the findAll() method that throws LazyInitializationException?
Don't get me wrong - I know where the LazyInitializationException comes from, but I just don't know what's the best way to get access to players if I sometimes need them when fetching clubs. Is my way of doing it correct?
There are 3 choices:
Access all the lazy fields inside a #Transactional method. You don't show your code, but there's usually a Service Facade layer which is responsible for being #Transactional. It invokes Repositories.
Write a query that fetches all the required data. Then you'd have to create a method specifically to fetch all the lazy fields required for that logic.
Use OpenSessionInViewFilter or OpenSessionInViewInterceptor so that Session/EntityManager are started before the execution even reaches the Controller. The Session then would be closed by the same high-level layer at the end of the request processing.
In addition to what Stanislav wrote, I'd like to elaborate on his 2nd point, because I think that this is often the best approach - that's simply because it saves unnecessary calls to the database which results in better performance.
Apart from writing separate JPQL query in your repository for each use-case, you could do one of the following .:
Make your repository extend JpaSpecificationExecutor and programmatically describe what needs to be fetched as described in this answer
Use Entity Graph either described using annotations, or programmatically, and fetch your entities using EntityManager as described in this tutorial
To optionally load what you want you can use EntityGraph.
Declare #NamedEntityGraph at your entity
#Entity
#NamedEntityGraph(name = "Club.players",
attributeNodes = #NamedAttributeNode("players")
)
public class Club {
Then you should annotate your findAll() method with this graph using it's name
#EntityGraph(value = "Club.players")
List<Club> findAll();
However that would override your basic findAll() method.
To avoid this (to have both implementations) you can follow this way:
Add dependency from https://mvnrepository.com/artifact/com.cosium.spring.data/spring-data-jpa-entity-graph/<version>
Then replace your repository with
#Repository
public interface ClubRepository extends JpaSpecificationExecutor<Club>, JpaRepository<Club, Long>, EntityGraphJpaSpecificationExecutor<Club> {
}
And then you'll have basic method findAll() and also from your sevice you can call
List<Club> clubs = clubRepository.findAll(specification, new NamedEntityGraph(EntityGraphType.FETCH, "Club.players"))

How to use Hibernate lazy loading and reactive streams?

I am currently building a REST service with Micronaut Data. I have defined two JPA entities, bounded by a bidirectional #OneToMany relationship, and lazy loading.
#Entity
#Getter
#Setter
public class ScoringEntity {
#Id
private String id;
#OneToMany(mappedBy = "scoring", fetch = FetchType.LAZY)
private Set<ContributionEntity> contributions;
}
#Entity
#Getter
#Setter
public class ContributionEntity {
#Id
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId
private ScoringEntity scoring;
}
#Repository
public interface ScoringRepository extends GenericRepository<ScoringEntity, String> {
Page<ScoringEntity> findAll(Pageable pageable);
}
In the controller, I return a Mono which is set to call the repository, then perform a mapping to a DTO (Scoring).
#Get
public Mono<Page<Scoring>> getScoring(Pageable pageable) {
return Mono.fromCallable(() -> scoringRepository.findAll(pageable))
.map(scoringEntities -> scoringEntities.map(scoringMapper::mapScoring));
}
Everything works fine in the findAll() method, but things go south when the mapper
tries to access the contributions set :
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ScoringEntity.contributions, could not initialize proxy - no Session
While I understand why this happens (the transaction probably ends with the repository method), I can't find a satisfying solution. Setting the relationship to eager loading works, but it
significantly decreases performance (and I've read elsewhere it would be a code smell, which I tend to believe).
Besides I can't imagine that reactive streams be incompatible with hibernate and lazy loading.
What would be the best practice in this situation ?
There are a few options:
Add #Join("contributions") annotation to your repository method
Add #EntityGraph ... annotation to your repository method
Do fetching and mapping in one method annotated #ReadOnly or #Transactional so the session is open when the mapper is called
In this case, having reactive streams doesn't make much sense. Just return Page from the controller annotated #ExecuteOn(TaskExecutors.IO).
Mapping the association as EAGER is usually a code smell. But if you have situations where you always need to load an association, you can do it via a query.
Instead of findAll, you would call:
#Repository
public interface ScoringRepository extends GenericRepository<ScoringEntity, String> {
#Query("select se from ScoringEntity se left fetch join se.contributions")
Page<ScoringEntity> findScoringWithContributions(Pageable pageable);
}
Besides I can't imagine that reactive streams be incompatible with hibernate and lazy loading.
In this case the problem is that you are trying to lazy load the association after the session has been closed.

How can we test for the N+1 problem in JPA/Hibernate?

I have a N+1 problem, and I’d like to write some kind of automated regression test because it impacts performance very much.
I thought about spying the EntityManager and verifying its method createQuery() is called only once, but Hibernate don’t use it to initialize lazy relationships, thus it didn’t work. I could also try to shut down the JPA transaction between my repository and my service (or detach my entity) and look out for exceptions, but it’s really an ugly idea.
To give us a frame, let’s say we have a very simple parent-child model:
#Entity
public class Parent {
…
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
private Collection<Child> children;
}
#Entity
public class Child {
…
#ManyToOne
private Parent parent;
}
And a very simple service:
public class MyService {
…
public void doSomething(Long parentId) {
Parent parent = …; /* retrieve Parent from the database */
doSomeOtherThing(parent.getChildren());
}
}
Parent retrieval from database could use the two following queries:
SELECT parent FROM Parent parent WHERE parent.id = :id;
SELECT parent FROM Parent parent JOIN FETCH parent.children WHERE parent.id = :id;
How may I write a test that crashes when I retrieve my Parent entity with the first query, but not the second?
As option you can verify count of queries (fetch, updates, inserts) in the test
repository.findById(10L);
SessionFactory sf = em.getEntityManagerFactory().unwrap(SessionFactory.class);
Statistics statistics = sf.getStatistics();
assertEquals(2L, statistics.getQueryExecutionCount());
See hibernate statistic
Refer to following solution, which relies on wrapping your DataSource
https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/
Another option available to you is to clear the EntityManager after your initial fetch, but before referencing any of the potentially lazy loaded fields on your entity. This effectively disconnects the proxies in place to perform lazy loading and should cause your test to throw an exception if JOIN FETCH wasn't used in the initial query.
Your test would end up looking something like the following (written in Kotlin)
class MyRepositoryTest #Autowired constructor(
myRepository: MyRepository,
entityManager: EntityManager
) {
#Test
fun `When load children will eagerly fetch`() {
val parent = myRepository.loadParent()
entityManager.clear()
// This line should throw if children are being lazily loaded
assertThat(parent?.children, equalTo(listOf(Child(1), Child(2))))
}
}
I suppose by "regression test" you mean an actual test probably started by JUnit.
A general way to handle that in a Unit-Test could be:
configure hibernate.show_sql to true
intercept the log-messages like described in intercept.
scan the log-file for
specific queries, you want to be avoided
number of similar queries
After running the query to retrieve a "parent" entity, using PersistenceUnitUtil, you can assert that "children" have or have not been eagerly loaded:
PersistenceUnitUtil pu = em.getEntityManagerFactory().getPersistenceUnitUtil();
assertTrue(pu.isLoaded(parent, "children"));
I've written a little library that can assert the count of SQL queries by type (SELECT, INSERT, ..) generated by Hibernate in your Spring tests, this way, you can be warned whenever the SQL statements change in your tests, and prevent N+1 selects. you can take a look here at the project
A test example that demonstrates the purpose:
#Test
#Transactional
#AssertHibernateSQLCount(selects = 1) // We want 1 SELECT, will warn you if you're triggering N+1 SELECT
void fetch_parents_and_children() {
parentRepository.findAll().forEach(parent ->
parent.getChildren().size()
);
}

Many to many bidirectional mapping in JPA

I have the following JPA entities.
A profile have many users and a user have many profiles:
#Entity
public class Profile implements Serializable {
#Id
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
private List<User> users;
...
}
#Entity
public class User implements Serializable {
#Id
private Long id;
#ManyToMany(mappedBy = "users")
private List<Profile> profiles;
...
}
On my application, when a user is merged, the profiles are updated on database.
However, when a profile is merged, the users are not updated.
Is possible to map my entities in order to make both sides merge their lists?
I am using JPA 2.1 and Hibernate.
Your Profile entity is ownind side or relationship. It's up to it, to manage relationship, so in order to update User you'll have to update Profile too or make manual SQL calls.
Java Specification for JPA 2.1 says that:
• For many-to-many bidirectional relationships either side may be the owning side
So if you'd like to make both entities editable from both side, remove mappedBy element and assigne necessacy cascade. But I'm not sure it works in Hibernate (didn't try actually), see this docs on mapping, there's no information about m:m without owning side: http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch07.html#collections-bidirectional
Otherwise, you may need to iterate through collection in Profile entity and then change them. For example:
for( User user : profile.getUsers() ) {
user.setSomething(.....);
}
session.merge(profile);
Changing List to Set might be needed in order to avoid Hibernate's delete and reinsert, described here: http://assarconsulting.blogspot.fr/2009/08/why-hibernate-does-delete-all-then-re.html
Also, don't forget about equals() and hashCode() methods override

Fetch data from table using hibernate with fetch=FetchType.LAZY

I'm trying to get all user's emails from table.
Entity user:
#Entity
#Table(name = "tbl_User")
public class User {
#Expose
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
.....
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
List<CommunicationAddress> communicationAddresses = new ArrayList<CommunicationAddress>();
.....
}
In the service I'm getting user and trying to look emails:
User user = userDAO.getUserById(id);
if (user == null) {
throw new Exception("User not found");
} else {
List<Email> addresses = user.getCommunicationAddresses();
}
But I received the next exception:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:186)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:137)
at org.hibernate.collection.internal.PersistentBag.isEmpty(PersistentBag.java:249)
The method for getting user:
#Transactional
#Override
public User getUserById(Long userId) {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(User.class);
criteria.add(Restrictions.eq("id", userId));
return (User) criteria.uniqueResult();
}
I understand that I must to get communicationAddresses when I get User using Criteria...
How to do it? Thank's all.
It seems your service method is not annotated with #Transactional. Thus, after calling userDAO.getUserById(id);, there is no longer a transaction. That means that you cannot access any lazy-loaded properties of the loaded entity that hasn't been accessed/pre-fetched inside the transaction without running into a LazyInitializationException.
So you can either think about replacing LAZY with EAGER fetching (this mostly depends on the use cases you are facing) or you should annotate your Service method with #Transactional.
I'd highly suggest to annotate your service methods (instead of the DAO methods), as only there you can establish meaningful transactional boundaries when interacting with multiple entities. Also, if you make usage of lazy loading, you must be aware of the possibility to run into that kind of exception after leaving the service layer, e.g. when rendering your view (assuming you somehow present the data).
"Prefetching' lazy associations
To trigger instant loading of lazy associations (called "dynamic association fetching"), add this line in getUserById:
criteria.setFetchMode("communicationAddresses", FetchMoode.EAGER);
However, if you do that in this specific method, I wonder why you stick to lazy loading at all?

Categories

Resources