Having difficulties mapping OneToOne relation of 1 parent class with 3 child classes - spring-boot

I have a parent class called FoodInfo, and 3 more child classes called Ingredient, Tag and MiscellaneousData. The relation between FoodInfo and each of those classes is OneToOne.
Currently, this is how I defined the classes:
FoodInfo:
#Entity
#Table(name="food")
public class FoodInfo {
#Id
#Column(name="code")
private Long code;
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
private Tag tag;
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
private Ingredient ingredient;
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
private MiscellaneousData misc;
//And the getters and setters for all attributes including:
public Ingredient getIngredient() {
return ingredient;
}
public MiscellaneousData getMisc() {
return misc;
}
public String getProduct_name() {
return product_name;
}
public void setTag(Tag tag) {
this.tag = tag;
}
public void setIngredient(Ingredient ingredient) {
this.ingredient = ingredient;
}
public void setMisc(MiscellaneousData misc) {
this.misc = misc;
}
}
In Ingredient's class:
#Entity
#Table(name="ingredient")
public class Ingredient {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(name = "code")
private FoodInfo foodInfo;
public FoodInfo getFoodInfo() {
return foodInfo;
}
public void setFoodInfo(FoodInfo foodInfo) {
this.foodInfo = foodInfo;
}
}
The other two child classes are the same as Ingredient.
And finally, to Insert all the data I did like so:
FoodInfo food = new FoodInfo();
Ingredient ing = new Ingredient();
MiscellaneousData misc = new MiscellaneousData();
Tag tag = new Tag();
//And after setting all their attributes...
food.setTag(tag);
food.setMisc(misc);
food.setIngredient(ing);
tag.setFoodInfo(food);
misc.setFoodInfo(food);
ing.setFoodInfo(food);
foodRepository.save(food);
Now, when I try to run the program, an error says:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource
[org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]:
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.guillermo.off.model.Ingredient.foodInfo in mappedBy of com.guillermo.off.model.FoodInfo.ingredient
............
Caused by: org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.guillermo.off.model.Ingredient.foodInfo in mappedBy of com.guillermo.off.model.FoodInfo.ingredient
In previous attempts, using annotations in a different way I managed to insert the data in the database, but when I tried to fetch all this data, the program got into an endless loop.
Any help will be very appreciated! Thanks in advance!!
EDIT:
After doing what #Hülya suggested, the information in the database seems to be right:
But when requesting the info, I ran into what looks an inifinite loop.
My code for requesting the data is:
#GetMapping("/food")
public List<FoodInfo> findFood(HttpServletResponse response) {
List<FoodInfo> food = foodService.findAll();
return food;
}
...and in the console, I can only see the folloging a thousand times:
at
com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
~[jackson-databind-2.11.4.jar:2.11.4] at
com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:755)
~[jackson-databind-2.11.4.jar:2.11.4]

#OneToOne annotation should be used on both parent and child side to create a bidirectional one-to-one mapping.
As the error says: Referenced property not a (One|Many)ToOne There is no mapping on the Ingredient side.
You should specify the entity association for foodInfo field with #OneToOne:
#Entity
#Table(name="ingredient")
public class Ingredient {
// ...
#OneToOne
#JoinColumn(name = "code")
private FoodInfo foodInfo;
}
Update for com.fasterxml.jackson.databind exception:
When serializing bidirectional relationships with jackson, cyclic dependency leads to an endless loop. To break the cycle you should add #JsonManagedReference and #JsonBackReference annotations:
FoodInfo class:
#Entity
#Table(name="food")
public class FoodInfo {
// ...
#OneToOne(mappedBy = "foodInfo", cascade = CascadeType.ALL)
#JsonManagedReference
private Ingredient ingredient;
}
Ingredient class:
#Entity
#Table(name="ingredient")
public class Ingredient {
//...
#OneToOne
#JoinColumn(name = "code")
#JsonBackReference
private FoodInfo foodInfo;
}

Related

Automatic JPA refresh ManyToOne objects with #Version feature

I'm getting an exception:
org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance
- save the transient instance before flushing :
com.example.jpamapstruct.entity.Member.club ->
com.example.jpamapstruct.entity.Club
while saving the member entity:
#Transactional
public MemberDto save(MemberDto memberDto){
Member entity = memberMapper.toEntity(memberDto);
return memberMapper.toDto(repository.save(entity));
}
How to fix this case in a proper way?
Possible solution:
I can get and set a club object before saving a member but is it only one and the best approach in such scenario?
Member entity = memberMapper.toEntity(memberDto);
clubRepository.getReferencedById(memberDto.getClubId()).ifPresent(entity::setClub);
return memberMapper.toDto(repository.save(entity));
Questions:
Should I put this getReferencedById code explicity? I mean what if we have several child objects (unidirectional ManyToOne), for each we need to get data from DB.
Is there any way to handle this by JPA (Spring Data/JPA) "automatically"?
Maybe it is possible to hit DB only one time with f.e join fetch somehow for all childs (with using custom #Query or querydsl or criteria/specification)?
Next, hoow to handle collections (unidirectional manyToMany)? In my case set of events in member object. Also need to loop thru and get all objects one by one before saving member?
Where should I put such logic in a service or maybe better in a mapstuct mapper?
If so, how to use repositories in such mapper?
#Mapper(componentModel = "spring")
public interface MemberMapper extends EntityMapper<MemberDto, Member> {
#AfterMapping
default void afterMemberMapping(#MappingTarget Member m, MemberDto dto) {
var club = clubRepo.findById(m.getClub().getId())
m.setClub(club)
}
Source code:
#Entity
public class Club extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
}
public class ClubDto extends AbstractDto {
private Long id;
}
#Entity
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
// commented out as don't want to save child object as it should already exist
// #ManyToOne(cascade = CascadeType.ALL)
#ManyToOne
Club club;
#ManyToMany
#JoinTable(name = "member_events",
joinColumns = #JoinColumn(name = "member_id"),
inverseJoinColumns = #JoinColumn(name = "event_id")
)
List<Event> events = new ArrayList<>();
}
public class MemberDto {
private Long id;
private ClubDto club;
}
#MappedSuperclass
public abstract class AbstractEntity {
#Version
private Integer version;
}
public abstract class AbstractDto {
private Integer version;
}
//MemberMapper above

Remove row from table throws ConstraintViolationException

Im having a problem when i want to delete the product from the database, deleting it, it should be removed from all the orders that contain that product. But when i try to do it this is the error i get:
"error_message": "Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [fkbjvki7e3gm7vrphs73g4x7d2g]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
This is my Order class:
#Entity
#Table(name="orders")
public class Order{
private #Id
#GeneratedValue
Long id;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL,orphanRemoval = true)
private List<ProductOrderDetails> orderedProducts = new ArrayList<>();
public void addProduct(Product product, int quantity) {
ProductOrderDetails orderedProduct = new ProductOrderDetails(this,product,quantity);
orderedProducts.add(orderedProduct);
product.getProductOrderDetails().add(orderedProduct);
totalOrderPrice+=product.getPrice()*quantity;
}
public void removeProduct(Product product,int quantity) {
ProductOrderDetails orderedProduct = new ProductOrderDetails( this, product,0);
product.getProductOrderDetails().remove(orderedProduct);
orderedProducts.remove(orderedProduct);
orderedProduct.setOrder(null);
orderedProduct.setProduct(null);
totalOrderPrice-=product.getPrice()*quantity;
}
}
This is my Product class
#Entity
#Table
public class Product {
private #Id
#GeneratedValue
Long id;
private String name;
#OneToMany(mappedBy = "order", cascade = CascadeType.MERGE,orphanRemoval = true)
private List<ProductOrderDetails> productOrderDetails = new ArrayList<>();
}
ProductOrderID
#Embeddable
public class ProdOrderId implements Serializable {
#Column(name = "order_id")
private Long orderId;
#Column(name = "product_id")
private Long productId;
}
Many to many column of Products and Orders
#Entity
#Table
public class ProductOrderDetails implements Serializable{
#EmbeddedId
#JsonIgnore
private ProdOrderId id;
#ManyToOne
#MapsId("orderId")
#JsonIgnore
Order order;
#ManyToOne
#MapsId("productId")
Product product;
private int quantity;
}
This is my controller method
#DeleteMapping("/{id}")
ResponseEntity<?> deleteProduct(#PathVariable Long id)
{
repository.deleteById(id);
return ResponseEntity.noContent().build();
}
I don't think this is doing what you think it's doing:
ProductOrderDetails orderedProduct = new ProductOrderDetails( this, product,0);
product.getProductOrderDetails().remove(orderedProduct);
If you debug your code or check the return value of remove you will find that it is returning false, which means nothing was removed.
You're just creating a new ProductOrderDetails and then trying to remove it from product.getProductOrderDetails(), but it doesn't exist in it. You need to find the right element to remove from that collection.

Spring + Hibernate without lazy = LazyInitializationException

I want to load all objects from a table without a lazy objects/children and list them on the page (Thymeleaf template), but I get a LazyInitializationException every time. I tried to convert Hibernate entity objects into a POJO that doesnt contains a lazy/unwanted object but with the same result. I also tried open-in-view parameter set to false...
Simple example:
Parent:
#Entity
public class DocumentDbe implements Serializable {
public DocumentDbe(){
}
#Id
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private DocumentFileDbe documentFile;
....
}
Child:
#Entity
public class DocumentFileDbe implements Serializable {
public DocumentFileDbe(){}
#Id
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column
#Lob
private byte[] documentData;
...
}
POJO:
public class DocumentDto implements Serializable {
public DocumentDto(){
}
public DocumentDto(DocumentDbe doc){
this.id = doc.getId();
}
....
}
Controller:
#GetMapping("/list")
String getList(Model model) {
List<DocumentDbe> docs;
List<DocumentDto> data = new ArrayList<>();
try (Session ses = sessionFactory.openSession()) {
docs = ses.createQuery("FROM DocumentDbe").list();
docs.forEach(doc -> {
data.add(new DocumentDto(doc));
});
}
model.addAttribute(MODEL_LIST_DATA, data);
return "list";
}
EDIT: Thrown exception:
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]")] with root cause
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
EDIT2:
In DocumentDbe is relation with another object (EAGER this time so I was not paying attention to it) , which has reference to DocumentDbe again.. chained relationship and LazyInitializationException is created...
EDIT3:
Although
This is modified and working controller, without POJO:
#GetMapping("/list")
String getList(Model model) {
List<DocumentDbe> docs;
try (Session ses = sessionFactory.openSession()) {
docs = ses.createQuery("FROM DocumentDbe ORDER BY id DESC").list();
docs.forEach(doc -> {
doc.setDocumentFile(null);
doc.getHistory().forEach(log ->{
log.setDocument(null);
});
});
}
model.addAttribute(MODEL_ADMIN_DATA, docs);
return "list";
}
In class DocumentDbe you have mark relation as Lazy. In default relation #ManyToOne and #OneToOne is as EAGER, so if you don't want Lazy, you have to change
#OneToOne(cascade = CascadeType.PERSIST)
If you want have #lob also as eager:
#Lob
#Basic( fetch = FetchType.EAGER )

Using Entity with OneToMany and HATEOAS RessourceAssembler leads to infinite recursion

I'm using two JPA entities annotated with #OneToMany (parent) <-> #ManyToOne (child) and I also wrote a RessourceAssembler to turn the entities into resources in the controller of a Springboot application (see below for code samples).
Without the relationship #OneToMany in the parent entity, Ressource assembling and serialisation works just fine.
As soon as I add the OneToMany relation on the parent the serialisation breaks with this:
"Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: org.springframework.hateoas.Resource[\"content\"]->com.marcelser.app.entities.Storage[\"storageStock\"])"
As you can see the infinite loop comes from the hateoas Resource, not the entities themselves.
I already tried to add #JsonManagedReference and #JsonBackReference on the entities or #JsonIgnore on the child but nothing really helps. The Hateoas RessourceAssembler always ends up in a infinite loop as soon as the child entity is embedded. It seems that shose #Json.... annotations help with the JSON serialisation of the entity itself but they don't solve problems with the RessourceAssembler
I have these 2 entities (Storage & Stock)
#Entity
#Table(name = "storage")
#Data
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;
}
#Entity
#Table(name = "stock")
#Data
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
and I'm using a RessourceAssemlber like follows for the parent entity 'Storage':
#Component
public class StorageResourceAssembler implements ResourceAssembler<Storage, Resource<Storage>> {
#Override
public Resource<Storage> toResource(Storage storage) {
return new Resource<>(storage,
linkTo(methodOn(StorageController.class).one(storage.getId())).withSelfRel(),
linkTo(methodOn(StorageController.class).all()).withRel("storages"));
}
}
and in the controller I have 2 get classes to list all or just a single Storage with its childs
public class StorageController {
private final StorageRepository repository;
private final StorageResourceAssembler assembler;
#GetMapping
ResponseEntity<?> all() {
List<Resource<Storage>> storages = repository.findAll().stream()
.map(assembler::toResource)
.collect(Collectors.toList());
Resources<Resource<Storage>> resources = new Resources<>(storages,
linkTo(methodOn(StorageController.class).all()).withSelfRel());
return ResponseEntity.ok(resources);
}
private static final Logger log = LoggerFactory.getLogger(StorageController.class);
StorageController(StorageRepository repository, StorageResourceAssembler assembler) {
this.repository = repository;
this.assembler = assembler;
}
#GetMapping("/{id}")
ResponseEntity<?> one(#PathVariable Long id) {
try {
Storage storage = repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
Resource<Storage> resource = assembler.toResource(storage);
return ResponseEntity.ok(resource);
}
catch (EntityNotFoundException e) {
log.info(e.getLocalizedMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body (new VndErrors.VndError("Storage not found", "could not find storage with id " + id ));
}
}
... omitted Put/Post/Delete
}
Can anyone enlighten me how I can solve this infinite loop in HateOAS. What I want is that the embedded child entries just either don't link back to the parent (so no links to parent are created) or they contain the link for the one level but no further processing is done.
To handle the problem related to the serialization of the model using Jackson API when the model attributes have a lazy loading defined, we have to tell the serializer to ignore the chain or helpful garbage that Hibernate adds to classes, so it can manage lazy loading of data by declaring #JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) annotation.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {...
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {...
or you can just declare unilaterally mapping commenting the Storage entity declaration and changing the private Storage storage; to fetch EAGER #ManyToOne(fetch = FetchType.EAGER) in Stock class.
#Entity
#Table(name = "storage")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Storage {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
/*#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "storage")
private Set<Stock> storageStock = new HashSet<>();;*/
}
#Entity
#Table(name = "stock")
#Data
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Stock {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "storage_id")
private Storage storage;
... other fields ommitted
}
Maybe a little late, but I've had this problem or very similar and I've only found one solution. The same error 500 gave me the clue on how to solve it:
Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.hateoas.PagedModel["_embedded"]->java.util.Collections$UnmodifiableMap["usuarios"]->java.util.ArrayList[0]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Usuario["rol"]->org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1["content"]->com.tfg.modelos.Rol$HibernateProxy$QFcQnzTB["hibernateLazyInitializer"])
So I only had to add in the application.properties:
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false

Infinite loop with spring-boot in a one to many relation

In a rest application, I use spring boot with jpa.
I have a class Lodger
who have
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "lodger")
private List<Reference> referenceList;
In my class Reference, i have
#ManyToOne
#JoinColumn(name = "lodgerId")
private Lodger lodger;
when i call this method
#RequestMapping(value = "/lodgers/{lodgerId}", method = RequestMethod.GET)
public Lodger getLogderById(#PathVariable("lodgerId") long lodgerId) {
return lodgerService.getLodger(lodgerId);
}
I get this error
org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (StackOverflowError) (through reference chain: server.bean.Lodger["referenceList"]->org.hibernate.collection.internal.PersistentBag[0]->server.bean.Reference["lodger"]->server.bean.Lodger["referenceList"]->org.hibernate.collection.internal.PersistentBag[0]->server.bean.Reference["lodger"]->server.bean.Lodger["referenceList"]...
Solution:
Use
#JsonManagedReference annotation for the first objects instantiated
#JsonBackReference annotation for the second objects instantiated
First:
#JsonManagedReference
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "lodger")
private List<Reference> referenceList;
Second:
#JsonBackReference
#ManyToOne
#JoinColumn(name = "lodgerId")
private Lodger lodger;
It happens when you have a cycle in return object and spring tries to serialize it to other type.
Try to create DTO or Value Object (simple POJO) without cycles from returned model and then return it.
If you primary keys in both tables are same name for example : id.
Add this
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class User {
...
}
And to Reference class.
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id")
public class Reference {
...
}
The only thing that you need is, in your class in which you have the annotation #ManyToOne, implement the next annotation with the attributes that you want to skip in the value section
#JsonIgnoreProperties(value = {"yourAttribute", "handler", "hibernateLazyInitializer"}, allowSetters = true)
I put an example for your code ->
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"referenceList", "handler","hibernateLazyInitializer"}, allowSetters = true)
#JoinColumn(name = "lodgerId")
private Lodger lodger;
All the attributes that you put in the value section on the #JsonIgnoreProperties are ignored, with this you can resolve the infinite loop and use it for other developments with the same format in the future.
Do not return entity with circular dependencies via REST webservice - create new DTO class, map entities fetched from database and return it in webservice.
More info here: http://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
Of course if you want you may use another mapping library, my personal favourite is Orika (http://orika-mapper.github.io/orika-docs/intro.html)
Lets assume your code looks like below :-
Lodger.class
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "lodger")
private List<Reference> referenceList;
public List<Reference> getReferenceList() {
return referenceList;
}
public void setReferenceList(List<Reference> referenceList) {
this.referenceList = referenceList;
}
#Override
public String toString() {
return "Lodger[referenceList=" + referenceList + "]";
}
Reference.class
#ManyToOne
#JoinColumn(name = "lodgerId")
private Lodger lodger;
public Lodger getLodger() {
return lodger;
}
public void setLodger(Lodger lodger) {
this.lodger = lodger;
}
#Override
public String toString() {
return "Reference[lodger=" + lodger + "]";
}
When you notice at the toString() method written in both the POJO's, you will see that we are calling toString() of both the classes from either side which results in infinite no. of calls to toString() method from both sides which never terminates. To avoid this situation remove any the reference from toString() of Refernce.class[You may remove from Lodger class also.] So toString() of Reference class will not have lodger property in it.
So finally your Reference class will look like :-
Reference.class
#ManyToOne
#JoinColumn(name = "lodgerId")
private Lodger lodger;
public Lodger getLodger() {
return lodger;
}
public void setLodger(Lodger lodger) {
this.lodger = lodger;
}
#Override
public String toString() {
return "Reference[Properties other than lodger=" + properties other than lodger + "]";
}
#JsonIgnoreProperties({"hibernateLazyInitializer","referenceList"}) at class Level
For reference see this article on medium.com.

Resources