I'm about to design my rest API. I wonder how can I handle objects such as one below:
#Entity
public class Foo {
#ManyToMany
private Set<Bar> barSet;
#OneToMany
private Set<Zzz> zzzSet;
}
As you see object I want to expose to my rest API consists of other entity collections. I'm using Spring 4 and Jackson. Is it possible to return objects like one above - or do I have to create classes with primitive values only?
Yes, it is possible but you have to handle 2 problems :
1) at serialization, Jackson will call the getter Foo.getBarSet(). This will crash because by default, Hibernate returns lazy collections for #OneToMany and #ManyToMany relationships.
If you don't need them, annotate them with #JsonIgnore :
#Entity
public class Foo {
#JsonIgnore
#ManyToMany
private Set<Bar> barSet;
#JsonIgnore
#OneToMany
private Set<Zzz> zzzSet;
}
If you need them, you must tell hibernate to load them. For example, you can annotate #ManyToMany and #OneToMany with fetch = FetchType.EAGER (It is not the only solution btw) :
#Entity
public class Foo {
#ManyToMany(fetch = FetchType.EAGER)
private Set<Bar> barSet;
#OneToMany(fetch = FetchType.EAGER)
private Set<Zzz> zzzSet;
}
2) It can also cause some infinite loops :
Serialization of Foo calls Foo.getBarSet()
Serialization of Bar calls Bar.getFoo()
Serialization of Foo calls Foo.getBarSet()
[...]
This can be handled with #JsonManagedReference and #JsonBackReference :
#Entity
public class Foo {
#JsonManagedReference
#OneToMany
private Set<Zzz> zzzSet;
And on the other side :
#Entity
public class Zzz {
#JsonBackReference
private Foo parent;
}
Related
I am using Spring Boot and Jackson and Hibernate to create an API. Hibernate connects to a MySQL database.
I understand the good practices but I'm stuck on a particular point.
I have an n:m relationship that contains an extra field.
Ex: Author(id, ...) -> Written(idAuthor, idBook, date) <- Book(id, ...)
I understand how to map a traditional n:m relationship, but this technique does not apply to me this time.
For this, I found a source on the internet that showed the solution: create an intermediate class in my code that contains an Author type object and a Book type object + my additional fields.
#Entity
#Table(name = "Author")
public class Author implements Serializable {
/...
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
private Set<Written> written= new HashSet<>();
/...
}
#Entity
#Table(name = "Book")
public class Book implements Serializable{
/...
#Id
#GeneratedValue
private int id;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
private Set<Written> written= new HashSet<>();
/...
}
public class Written implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "idAuthor")
private Author author;
#Id
#ManyToOne
#JoinColumn(name = "idBook")
private Book book;
//Extra fields ....
}
That's a bidirectional link.
With this code, I get an infinite recursivity error:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.exampleAPI.api.model.Book["written"])]
I tried to use #JsonIgnore, #JsonManagedReference and #JsonBackReference on the Written class, also tried to use transient keyword, but nothing worked.
I can't find any source on the internet that could help me, and neither can the documentation for this particular case.
Can someone help me?
When unhandled bidirectional relationship occurs, Jackson faces infinite recursion.
I tried to use #JsonIgnore, #JsonManagedReference and #JsonBackReference on the Written class
You need to use #JsonManagedReference and #JsonBackReference annotations separately to prevent these cycles between Book and Written. A side note, transient has nothing to do with the persistence but the serialization. JPA works with the #Transient annotation.
public class Book implements Serializable {
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
#JsonBackReference
private Set<Written> written= new HashSet<>();
...
}
public class Written implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "idBook")
#JsonManagedReference
private Book book;
...
}
Important: Don't send database entities through REST (probably what you are up to do). Better create a DAO object without bidirectional relationship and map entities into DAOs. There are several libraries able to do that: I highly recommend MapStruct, however ModelMapper is also an option. If there is a lower number of such entities, using constructors/getters/setters would be enough.
I have two tables in database orders and offers. Earlier there was #OneToOne mapping between two i.e. for a single order, there was a single offer. Corresponding domains are:
#Entity
#Table(name = "orders")
#DiscriminatorFormula("0")
#DiscriminatorValue("0")
class Order {
#OneToOne(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Offer offer;
public Offer getOffer() {
return this.offer;
}
public void setOffer(Offer offer) {
this.offer = offer;
}
}
#Entity
#Table(name = "offers")
class Offer {
}
Now, i want OneToMany mapping between two i.e. for a single order, there can be multiple offers now. But for that, i want to build new version of Domain so as not to effect existing functionality. As it is OneToMany mapping so i will have to use Set or List. So, effectively, i want did:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offer;
public Set<Offer> getOffer() {
return this.offer;
}
public void setOffer(Set<Offer> offer) {
this.offer = offer;
}
}
How can i achieve this as currently it is giving me error in getter method as overridden method cannot have different return type.
Actually your problem is that you are using a field with the same name offer as the field in the super class while both have different types, so it will be confusing because you will have the child getter for Set<Offer> overriding the parent getter for Offer, that's why you get the Exception:
error in getter method as overridden method cannot have different return type
What you will have to do here is to use a different name for the field in your child class, for example offers, so the Model will be correct and Hibernate will correctly map the objects:
#Entity
#DiscriminatorValue("00")
class OrderV2 extends Order {
#OneToMany(mappedBy = "order1", fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
private Set<Offer> offers;
public Set<Offer> getOffers() {
return this.offers;
}
public void setOffers(Set<Offer> offers) {
this.offers = offers;
}
}
Note:
You need to have two objects of type Order in your Offer class, one for the mapping of offer and the second for the offers mapping, notice the mappedBy = "order1" in the mapping.
I have the two entities similar to the ones below:
#Entity
#DynamicUpdate
public class EntityOne {
#Id
public long id;
public String name;
public String displayName;
#JsonManagedReference
#OneToMany(mappedBy = "id", cascade = CascadeType.ALL)
#OrderBy(value = "name")
public List<EntityTwo> entityTwoList;
}
entity two is
#Entity
#DynamicUpdate
public class EntityTwo {
#Id
public long id;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "id")
public EntityOne entityOne;
}
service:
Lists.newArrayList(repository.findAll());
The service calls the findAll() method of the CRUDRepository. I don't want the list of entity two objects to load when calling findall() but this solution doesn't work.
Anything incorrect that i am doing here to lazy load the collection. I basically don't want the collection to be loaded until its specified.
By default the mappings are lazy load.
I am assuming jackson is trying to load your child objects.
Try followjackson-datatype-hibernate4:2.4.4
Add jackson-datatype-hibernate4:2.4.4 to your dependency and define following bean
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(Hibernate4Module.class);
}
I don't know about jackson but according to jpa specs you can not force LAZY loading neither can you rely upon it ... you just provide the lazy load hint and it's totally up to the provider to load it lazily or not (unlike EAGER , it forces loading)
First try this,i think you need to specify fetch type lazy or Eager
ManyToOne(fetch= FetchType.LAZY)
I have a requirement to combine 2 entity objects into 1 model object that will be used to return data back to a calling api method. Is there a common pattern or solution for this scenario?
#Entity
public class Entity1{
#Column
private String value1;
#Column
private String value2;
}
#Entity
public class Entity2{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "value1")
private Entity1 entity1;
#Column
private String value3;
}
For me you should use a Facade or a Business Delegator, that should return the object like a Adaptor of the two.
Entity2 already has Entity1, so it's sufficient to return an instance on entity2.
If you want to simplify your API, you should make a new model that combines properties of both and do the mapping behind the scenes.
I have a class, which can be referenced by many different classes in a Many-To-One or One-To-One relationship or have no referencing object. When an element of this class is deleted, the objects pointing to it should be removed too. What is the most beautiful way to achieve this behavior?
class A {
public remove() {
// remove the element which is pointing to me
}
}
class B {
#ManyToOne
private as
}
class C {
#ManyToOne
private as
}
...
First Of All, I don't think it is a 'beautiful' solution to put business methods in your entity class.
I would recommend creating DAO object for your A class and make your relationship bi-directional with CascadeType set to REMOVE:
#Entity
class A {
#OneToMany(mappedBy = "parentB", cascade = CascadeType.REMOVE)
private Set<Child> childrenB;
#OneToMany(mappedBy = "parentC", cascade = CascadeType.REMOVE)
private Set<Child> childrenC;
}
#Stateless
class daoA {
#PersistenceContext
EntityManager em;
public void remove(A a){
em.delete(a);
}
}