Hibernate: lazy loading vs dtos - spring-boot

Imagine having an JPA entity with different lazy loaded fields
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "organisation_teams",
joinColumns = #JoinColumn(name = "id", referencedColumnName = "id", table = "usr_organisation"),
inverseJoinColumns = #JoinColumn(name = "team_id", referencedColumnName = "id", table = "usr_teams")
)
private Set<Team> teams = new LinkedHashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "organisation_associations",
joinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id", table = "usr_organisation"),
inverseJoinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id", table = "organisation")
)
private Set<Organisation> children = new LinkedHashSet<>();
This entity is now requested by a second service, so we need to convert into a DTO and return via REST for example. Now there can be different scenarios, where for example only the team field is required, only the children field or both.
One could map them always into the DTO, in this case using lazy loading is pointless in my opinion. Are there different elegant options aside of creating different DTOs (containing only the team or only the children fields) and different REST endpoints? I don`t think this would be an elegant solution as well, because you would end up with a bunch of endpoints and dtos.
Additionally Team and Organisation again have lazy loaded fields and the same issue applies again.

I think that what you are looking for might be https://graphql.org/. But for plain REST endpoints I would say different DTOs is the way to go, use inheritance to avoid code duplication.
If you want to improve performance and not lazy load related entities you can specify different named entity graphs and depending on the DTO select the one that eagre loads the required data. See the documentation here: https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs001.htm.
Any lazy loaded field that is not mapped to a DTO won't be loaded. So basically you use the same entity with different graphs and different DTOs.

Related

Hibernate JoinTableFilter annotation

I'm using Hibernate to map my entities and I'm having a problem. I want my entity to retrieve a list of entity from another table, linked with a join table. I also want to filter on the join table to only retrieve objets whose boolean_value is set to false.
It currently works without filtering, using #JoinTable annotation. I'm facing difficulties when it comes about #FilterJoinTable which seems not to be working.
Here is what I tried to do:
#Entity
#Table(name = "table_a")
#FilterDef(name="checkValue")
#Filter(name = "checkValue")
public class AEntity {
// id ...
#ManyToMany
#JoinTable(name = "my_join_table",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id"))
#FilterJoinTable(name = "checkValue", condition = "boolean_value = FALSE")
private List<BEntity> objets;
}
Currently it returns all the objects from table_b without filtering.
Any idea of what I'm doing wrong ?
You have to use the #Where annotation, as #FilterJoinTable allows defining Hibernate filters which have to be enabled explicitly with Session#enableFilter(String):
#ManyToMany
#JoinTable(name = "my_join_table",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id"))
#Where(clause = "boolean_value = FALSE")
private List<BEntity> objets;

Spring/JPA twice uniderectional not working, because of #JoinColumn(nullable = false)

I have problem with unidirectional mapping and need help.
I have 2 Entities with the same unidirectional mapping.
The first one:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "massnahme_id", nullable = false)
private Set<VerortungDAO> verortungen = new LinkedHashSet<>();
The second one:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "massnahmen_verbund_id", nullable = false)
private Set<VerortungDAO> verortungen = new LinkedHashSet<>();
If I try to save one Entity, hibernate throws an Exception because of the second Entity definition (not null).
org.hibernate.PropertyValueException: not-null property references a
null or transient value
If I change the JoinColumn to nullable = true, then the unidirectional mapping not working and the list is not saved in DB.
What can I do to make it work?
Make the associations bidirectional and map the to-one association in VerortungDAO, or if you don't want that, at least map the FK-columns. If you map it bidirectional, use #OneToMany(mappedBy = "..."). Either way, you will have to initialize the two to-one associations or FK-columns on the VerortungDAO objects.
PS: An entity isn't a DAO (data access object), so the naming xxDAO is quite confusing for an entity.

In many to many get only id instead of the whole object

public class Role {
#ManyToMany
#JoinTable(name = "user_to_role",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id",referencedColumnName = "id"))
private Set<User> users;
}
public class User {
#ManyToMany
#JoinTable(name = "user_to_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
I have a many to many relationship between the two classes. When calling role.getUsers(), I want to get only the user ids, the rest of the fields should be ignored, since there will be a lot of data and I don't want to load everything, How can I achieve this?
A straightforward way to do it would be to use a Criteria query, but to use it inside an Entity, you'd have to inject an EntityManager there, which is considered a bad practice. A better solution would be to create this query in a Service.
But if you still want to do it, then your getUsers method would look something like this:
public List<User> getUsers() {
Criteria cr = entityManager.createCriteria(User.class)
.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.setResultTransformer(Transformers.aliasToBean(User.class));
List<User> list = cr.list();
return list;
}
If you want to restrict your list, just use a Restrictions, like so: criteria.add(Restrictions.eq("id", yourRestrictedId))
Since you have mapped the entities User and Role using #ManyToMany relationship, you need to create a DAO/Service class to implement the business logic to filter only userIds and return the same.
This cannot be handled in your Model\Entity classes as it will defy the whole concept of Object-Relational mapping.
I can create the business logic using DAO for your example if you want but you will get 10's of blogs achieving the same.
For your reference,you can check my sample project here.

Hibernate #OneToMany don't remove child from list when updating parent

I have create mapping between Paper and Mcq question as below.
public class Paper {
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "paper_mcq",
joinColumns = {#JoinColumn(name = "paper_id")},
inverseJoinColumns = {#JoinColumn(name = "mcq_id")})
#JsonIgnore
private Set<Mcq> mcqs = new HashSet<>();
}
When I'm updating Paper entity it's deletes all MCQ.
SQL Output:
Hibernate: delete from paper_mcq where paper_id=?
I believe your paper object in paperRepo.save(paper) don't have mcqs at this time, and the cascading sees that as a deletion. I'm just assuming that you're receiving your object from json and the #JsonIgnore simply ignores the deserialization.
So there are multiple options to solve that:
- Query the mcqs and set them before updating
- remove #JsonIgnore and add those in your json
- remove the cascading and set it manually

Insert data in intermediate table created by spring

I am using spring with JPA and I have 2 model classes
User
Resources
I have defined many to many relation using -
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "users_resources", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "resource_id", referencedColumnName = "id"))
This definition has created another table users_resources.
To insert data in users_resources table, the REST api is sending just the User id [primary key] and Resource Id [primary key].
Is there anyway I can insert data into users_resources without fetching full user object and resource object? I wanted to use native query but it seems PersistenceJPAConfig has noway to run native INSERT query.
If you know Id of the object, and you need object reference (without object details) you could use EntityManager.getReference()
User userReference=em.getReference(User.class,userId);
Resource resourceReference=em.getReference(Resource.class,resourceId);
Using references will not exectue any SELECTs from the database.

Resources