Hibernate: avoid implicit initialization of a collection - java

We're using entity classes for two purposes:
As a database model, i.e. Hibernate #Entity
As a data model sent over to the front end as JSON
Imagine an #Entity has a number of bulky collections, e.g.:
#Entity
#Table(name = "customer")
public class Customer {
#Id
private Integer id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Order> orders;
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Counterparty> counterparties;
/* ... a lot of other properties, including collections ... */
}
Now, we work with Customer in two major ways:
Fetch an individual customer for viewing/editing, complete with all collections.
Fetch a list of customers, with just their "core" properties required for display; we don't need any of its child collections loaded as we're not using them and they generate a lot of additional queries.
My preferred solution would be explicit initialization of collections for the case 1, and keeping them null or empty for the case 2.
The problem is that when Jackson serializes an object into JSON, it goes over all properties, including collection ones, so they are forced to be initialized. Adding #JsonIgnore is not an option (we need those collections for case 1), adding #Transient to keep Hibernate away from them is not an option either (we have to be able to store collections after editing).
Another alternative would be, of course, creating a different model of Customer without collections and using that for scenario 2, but that means maintaining two varieties of the same entity and I'd like to avoid that.
How can I disable Hibernate's implicitly loading of these collections, so that they are only initialized explicitly (via e.g. Hibernate.initialize(customer.orders)), while retaining the possibility of persisting them when needed?

There is no way to do it in Hibernate.
Alternatives:
Use DTOs. This has the advantage of tailoring the objects to be serialized to the exact needs of the client consuming the resulting JSON. Also, the domain model (Hibernate entities) is decoupled from the serialization logic, allowing the two to evolve independently.
Use custom serializers.

Eventually I've solved it in the "DTO-fashion" but with minimal overhead, by adding an alternative getter for each property, like so:
#Entity
#Table(name = "customer")
public class Customer {
...
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
private List<Order> orders;
/* "Normal" getter to be used in the back-end */
#JsonIgnore // <-- Important as it hides it from Jackson
public List<Order> getOrders() {
return orders;
}
/* Non-initializing getter for serialization for the front-end */
public List<Order> getOrdersNonInit() {
return Hibernate.isInitialized(orders) ? orders : null;
}
...
}

The following is just one possible solution and depends on your environment.
There's a project called "jackson-datatype-hibernate" from the FasterXML project. You can find it at https://github.com/FasterXML/jackson-datatype-hibernate which allows to define a custom serializer with includes disabling of lazy fields. (I think this default for this serializer).
Code could look like the following:
ObjectMapper mapper = new ObjectMapper();
Hibernate4Module hibernateModule = new Hibernate4Module();
mapper.registerModule(hibernateModule);

Related

Hibernate, bidirectional relation when using MapStruct

Sample code to represent my problem.
Let's say there are entity classes like that
public class User {
...
#ManyToOne
#JoinColumn(name = "users_statuts_id", referencedColumnName = "id", nullable = false)
private UserStatus status;
}
public class UserStatus {
...
#OneToMany(mappedBy = "status")
private List<User> users = new LinkedList<>();
}
This works as expected but, problem starts when I want to use MapStruct generated mapper and DTO object. I will get StackOverflowError becouse of Cyclic references User->UserStatus->List< User>-> .....
And take into account that not necessary it will be in pattern A->B->A (User->UserStatus->User)
sometimes it will be User->ClassA->ClassB->...->User
I tired adding Context (CycleAvoidingMappingContext) to mapper as stated in other threads to break cycle but I failed and still get StackOverflowError. Tried too with AfterMappings and Mapping(ignore) but still not worked and I would like to avoid settings manually nulls to break the cycle.
I wonder if is possible to break cycle similar to #JsonIdentityInfo but on Hibernate level ?
My classes are a bit more complicated and bidirectional relation would help a lot later. Someone have some tips how should I make it done to work properly ?

How to audit class that has OneToMany unidirectional relationship?

I'm using Spring Data JPA for Auditing. There's a unidirectional relationship between classes Article and File. The Article class looks like this:
#Getter
#Entity
#SuperBuilder(toBuilder = true)
#Table(name = "article")
public class Article extends AuditEntity {
...
#Builder.Default
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true)
#JoinTable(name = "article_additional_file",
joinColumns = #JoinColumn(name = "article_id"),
inverseJoinColumns = #JoinColumn(name = "additional_file_id"))
private List<File> additionalFiles = new ArrayList<>();
...
}
The problem is, when changes occur in the file list (owned files get deleted or added), the modifiedDate field (which is in AuditEntity class and it's annotated with #LastModifiedDate annotation) is not updated (it works with all other fields). And I cannot make it a bidirectional relationship since other classes own the File class as well. So my question is, how to trigger the update of field modifiedDate when changes occur in the file list?
EDIT
I'd prefer not to use Enver, if that's possible. I need to use as little additional libraries as possible
Instead of using #JoinTable use #AuditJoinTable
Info from the hibernate documentation:
When a collection is mapped using these two annotations (#OneToMany + #JoinColumn), Hibernate doesn't generate a join table. Envers, however, has to do this, so that when you read the revisions in which the related entity has changed, you don't get false results.
To be able to name the additional join table, there is a special annotation: #AuditJoinTable, which has similar semantics to JPA's #JoinTable.

Springboot JPA entity is auto loading lazy join entities

I have an entity as
#Getter
#Setter
#Entity
#Table(name = "feature")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Feature {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#OneToMany(mappedBy = "featureId", fetch = FetchType.LAZY)
private transient Collection<FeatureComponent> components;
}
While in its Repository(Dao) file, I have
public interface FeatureDao extends JpaRepository<Feature, Integer> {
#Query("SELECT e FROM Feature e")
public List<Feature> getAll();
#Query("SELECT e FROM Feature e LEFT JOIN e.components fc WHERE e.id= :id")
public Feature getWithDetail(#Param("id") Integer id);
}
When I'm calling featureDao.getAll(); it returns all features but including components list filled and because of that, my response it being too large to load on client-side.
I'm unable to understand why it is happening when I'm using Lazy fetch mode and didn't mentioned joining with components in my getAll method.
Please help to resolve that issue,
Thanks in advance.
Just like #spOOm already mentioned I also suspect this is the side effect of Jackson Feature entity serialization into JSON triggering the load of all the components.
That is why using DTOs instead of plain Entities is usually advisable when returning data via a Controller. With DTOs, you clearly define whatever you want to include in the response to the caller. You can even reorganize your model so that it fits better the needs of the clients. You decouple your inner model and the model your API consumers know, making it possible to rework your inner model and still keep the same public model. You could have the following DTO.
public class FeatureSimpleDto {
private Integer id;
private String name;
private String description;
}
Then, in your Controller or Service (here you can find different opinions) you would basically convert your Feature entity into a FeatureSimpleDto that would be returned by your Controller. Here you can use mapping libraries such as MapStruct or you can do it on your own (I usually tend to prefer doing it on my own, one less dependency to rely on).
Using Lombok may be a problem, depending on the relationship between tables, try to create getters manually in entity classes.
Thanks to everyone for providing workarounds... But every work item requires lots of changes which were not possible for me...
Luckily I found a solution that is working fine for me... Better to post here...
Step-1: Add dependency in pom.xml file
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
Step-2: Add a 'Bean' for Hibernate Module
We can add bean in any file having #Configuration annotation... Even, we can add in Springboot main application file(where we have main method).
#Bean
public Module datatypeHibernateModule() {
return new Hibernate5Module();
}
That's it, Happy Coding...

jpa - Multiple #ElementCollection in two different #Embeddable classes

I have an entity with two Embedded classes of the same type and which one has an ElementCollection of the same type two. The business logic is apparently correct, but I am experiencing some problems with lack of knowledge in JPA, I guess.
Let's check my classes:
#Entity
public class Etapa extends EntidadeBase {
#Embedded
private CronogramaDeDesembolso cronogramaDeReceita;
#Embedded
private CronogramaDeDesembolso cronogramaDeDespesa;
}
#Embeddable
public class CronogramaDeDesembolso {
#ElementCollection
private List<Parcela> parcelas;
}
I am receiving the following error log.
Caused by: org.hibernate.HibernateException: Found shared references
to a collection:
nexxus.convenioestadual.dominio.planodetrabalho.etapa.Etapa.cronogramaDeReceita.parcelas
Do you guys have any clue of what is wrong and how can I fix it?
EDIT:
Due comments I did this edit and it do not worked too
#Entity
public class Etapa extends EntidadeBase {
#Embedded
#AttributeOverride(name = "parcelas", column = #Column(name = "parcelasReceita"))
private CronogramaDeDesembolso cronogramaDeReceita;
#Embedded
#AttributeOverride(name = "parcelas", column = #Column(name = "parcelasDespesa"))
private CronogramaDeDesembolso cronogramaDeDespesa;
}
Is there any reason why you have decided to use this structure ? Typically when converting an object to an RDBMS you would need to model the relationships. When you use an embeddable it will add the column (or columns) associated with it to the table. So when you do this normally (not collections) it is fine.
When you do a collection it runs into issues. Mainly there is no way to represent a collection in a single row (since this is an entity you could have many of them so effectively for each object you only have one row) & one column. So when you represent a collection you actually have to have a second table with a column referencing it back to the first. It's really the opposite thinking of a normal object. The collection entries need to know what collection they were associated with instead of the collection being knowledgeable of its entries.
So in some POJO you could have and these....
MyListObject {
//Some implementation of things you want to collect
}
MyClass {
List<MyListObject> myListObject;
}
But to model this in JPA you would need to have these represented by two tables.
Your object that will be in the list.
#Entity
MyListObject {
#ManyToOne
#JoinColumn(name = "MY_CLASS_KEY")
private MyClass myClass;
}
Your object/entity that will have the list.
#Entity
MyClass {
#Id
#Column(name = "MY_CLASS_KEY")
private Long myClassKey;
#OneToMany(mappedBy = "myClass")
private List<MyListObject> myString;
}
I hope this helps.
A quick search on Google turned up this in StackOverflow:
JPA Multiple Embedded fields
It would seem as though you have to do some explicit annotation overriding over the fields within the embeddable class. There are some code examples in the linked answer as well that should give you a good idea of where to go.
Cheers,

How do I prevent Hibernate from deleting child objects not present in a JSON post?

I have a JPA Property entity that has children (multiple Rate's and multiple Reservation's). In my JavaScript application, I pull JSON via REST {property:{rates:[...], reservations[...]}. Rates and Reservations are very verbose, so when I post a property update (like changing the name), I delete the rates and reservations from the JSON POST payload. I hoped that Hibernate would simply ignore the missing keys, but alas, it's removing all the children on save. How do I specify to Hibernate to ignore them if they're not there?
#Entity
public class Property {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
String name;
#OneToMany(mappedBy = "property", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JsonManagedReference
private Set<SeasonRate> rates = new HashSet<>();
#OneToMany(mappedBy = "property", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Set<Reservation> reservations = new HashSet<>();
}
Ps: My understanding of cascades is limited, but I do actually want the functionality that if someone deletes a property, it must delete the rates and reservations. Nowhere do I update rates or reservations via a full property save though, so perhaps I should just use CASCADE=UPDATE? Rates have their own update mechanism and so do reservations.
Late answer, however if you are using a Spring MVC Rest controller to handle the update rather than a Spring Data Rest controller then it would appear the former does not support partial updates/patch requests.
Working with traditional non-restful web apps modifying entities directly via a form this is of course possible. For example:
#RequestMapping
public String updateEntity(#ModelAttribute myEntity){
//submitted form parameters merged to existing entity
//loaded via getMyEntity() leaving unmodified fields as they were
}
#ModelAttribute
public MyEntity getMyEntity(){
//load some existing entity
}
However when binding JSON to an Entity via #RequestBody this is not possible:
#RequestMapping
public String updateEntity(#RequestBody myEntity){
//new instance instantiated by the Jackson mapper
//missing fields will be null
}
There are some outstanding JIRAs around this:
https://jira.spring.io/browse/SPR-13127
https://jira.spring.io/browse/SPR-10552
https://jira.spring.io/browse/SPR-13127
And various SO questions:
Spring Partial Update Object Data Binding
The good news however is that Spring Data Rest does support partial updates via patch so if it is an option to expose your repository as a Rest Repository then you should be able to achieve the required behaviour:
https://spring.io/guides/gs/accessing-data-rest/
PUT replaces an entire record. Fields not supplied will be replaced
with null. PATCH can be used to update a subset of items.

Categories

Resources