Husband.java
package com.example.demo.com.example.domain;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
//#Data
//#NoArgsConstructor
//#EqualsAndHashCode
//#ToString
#Entity
#Table(name = "t_husban")
public class Husband {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String job;
#OneToOne
#JoinColumn(name = "wife_fk",referencedColumnName = "id")
private Wife wife;
//omitted getter/setter
}
Wife.java
package com.example.demo.com.example.domain;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
//#Data
//#NoArgsConstructor
#EqualsAndHashCode(exclude = "husband",callSuper = false)
#Entity
#Table(name = "t_wife")
public class Wife {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToOne(mappedBy = "wife",cascade = {CascadeType.ALL})
private Husband husband;
//omitted getter/setter
}
Service.java
#Service
public class TestOneToOneEitherSide {
#Autowired
private WifeRepository wifeDao;
#Autowired
private HusbandRepository husbandDao;
public Husband testCreate() {
Husband husband = husbandDao.findByName("Wang");
return husband;
}
}
When I query husband from database using spring data jpa,it occurs nfinite recursion in the result,seeing the follow picture.What's something wrong while using #OneToOne annotation?Could anybody give me some advice? Or I use the annotation in wrong way.
the picture
This is a known issue, when you have bidirectional relation jackson will try to serialize each reference of one side from the other side so its logical to have infinite recursion.
Solution: There are many solutions to that , you could use #JsonIgnore on one side to avoid serializing the annotated reference hence breaking the infinite recursion
#EqualsAndHashCode(exclude = "husband",callSuper = false)
#Entity
#Table(name = "t_wife")
public class Wife {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToOne(mappedBy = "wife",cascade = {CascadeType.ALL})
#JsonIgnore
private Husband husband;
//omitted getter/setter
}
you also could use #JsonManagedReference/#JsonBackReference, check this link for more info how to use them
This answer has one problem , if you try to serialize wife direction you will not have husband object since the solution was to avoid serializing it.
There is a nice solution to this, its mentioned in this link , the idea is to generate a reference to the parent entity, so if you are serializing husband, you will have husband->wife->[reference to husband instead of husband], all you need to do is to annotate your entities with #JsonIdentityInfo
#EqualsAndHashCode(exclude = "husband",callSuper = false)
#Entity
#Table(name = "t_wife")
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="#id")
public class Wife {
#JsonIdentityInfo(generator=ObjectIdGenerators.UUIDGenerator.class, property="#id")
#Entity
#Table(name = "t_husban")
public class Husband {
#Id
You Should use #JsonBackReference with owning entity and #JsonManagedReference with child containing entity classes.
#JsonManagedReference
#Onetoone
private Queue queues;
#Onetoone
#JoinColumn(name = "qid")
// #JsonIgnore
#JsonBackReference
private Queue_group queue_group;
You can use lombok's #ToString.Exclude on the husband field or wife field to break the cycle references, either jackson will try to serialize each reference back and forth infinitely.
#OneToOne(mappedBy = "wife",cascade = {CascadeType.ALL})
#ToString.Exclude
private Husband husband;
or
#OneToOne
#JoinColumn(name = "wife_fk",referencedColumnName = "id")
#ToString.Exclude
private Wife wife;
In my opinion, the best way to avoid this kind of problems is to create DTO(Data Transfer Object) or simple pojos to your Entity classes. And dont experiment with json serialization management or messing Entities with as #Transient options)
Use Entities only for data persistance as Jpa stands for. (Use Entity to hold and manage data) and use DTOs or POJOs to transfer and communicate over api, etc. Furthermore I use many kind of Dtos for one giant Entity to make things easier to fetch some specifical works.
Related
I have a pretty annoying use case that is a puzzle.
I have the following composite key (Embeddable):
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Getter
#Embeddable
public class DefaultFilterId implements Serializable {
#Column(name = "AUTOM_PLAN_TYPE_TCD_ID")
Long planTypeId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "FILTER_TYPE_TCD_ID", referencedColumnName = "FILTER_TYPE_TCD_ID")
FilterTypeEntity filterType;
#Column(name = "VOLGORDE")
Long order;
}
My entity looks like this:
#Entity
#Table(name = "ZVZ_PLAN_T_FLTR_T_DEF")
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DefaultFilterEntity {
#EmbeddedId
private DefaultFilterId defaultFilterId;
private Long planTypeId;
#MapsId("filterTypeId")
private Long filterType;
private Long order;
}
Basically I want a composite primary key on the 3 fields, not sure how to use mapsId on the id (Long value) of the FilterType.
The JPA query is simple:
#Repository
public interface DefaultFilterRepository extends JpaRepository<DefaultFilterEntity, Long> {
List<DefaultFilterEntity> findDefaultFiltersByPlanTypeId(Long planId);
}
It's a pain really, almost on the brink of just writing an SQL query which would take me 2 seconds :-/
Anyhow the query fails because the select query isn't correctly generated; in the hibernate log I've seen that the property names are included in the select, while they obviously don't exist.
So how do I do this?
Edit: I gave up on the convoluted embeddable shizzle.
Went with:
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
#Getter
public class DefaultFilterId implements Serializable {
Long planTypeId;
FilterTypeEntity filterType;
Long order;
}
#Entity
#Table(name = "ZVZ_PLAN_T_FLTR_T_DEF")
#IdClass(DefaultFilterId.class)
#AllArgsConstructor
#NoArgsConstructor
#Getter
public class DefaultFilterEntity {
#Id
#Column(name = "AUTOM_PLAN_TYPE_TCD_ID")
private Long planTypeId;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "FILTER_TYPE_TCD_ID", referencedColumnName = "FILTER_TYPE_TCD_ID")
FilterTypeEntity filterType;
#Id
#Column(name = "VOLGORDE")
private Long order;
}
This seems to generate the correct hibernate query, however it returns an empty list from my jpa query. When performing the hibernate query directly on the db it shows the correct resultset.
(Well correct, not really, as I don't see JOIN being made to get the joined filterTypes).
Any idea?
Fixed this issue by adding a surrogate key to the table.
Hibernate is pleased, and all worked instantly.
In case devs don't have ability to add a surrogate key to the table for some reason, I wish you good luck.
I have 3 entities -
Course
Module
Timeline
Course is an independent entity with following attributes:
Course - (id Integer Primary Key, course_name)
#Id
#Column(name = "id")
Integer courseId;
#Column(name = "course_name")
String course_name;
Next up is another entity Module,
Every row in module is related to one course, and hence there is a one to one relationship between Module and Course.
Module - (module_id, module_name, module_type, duration)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "module_id")
Integer module_id;
#Column(name = "module_name")
String module_name;
#Column(name = "duration")
Integer duration;
#ManyToOne
#JoinColumn(name="timeline_id", nullable=false)
private Timeline timeline;
Now, next is a timeline entity, which is also related to course i.e every timeline id belongs to one course id, but one timeline id can belong to multiple module_ids, and hence below code:
#Id
#Column(name = "timeline_id")
Integer timelineId;
#OneToMany( mappedBy = "timeline" )
private List<Module> module;
#OneToOne( cascade = CascadeType.ALL)
private Course course;
Can you please tell me what is the error over here.
ModuleRepository:
#Repository
public interface ModuleRepository extends JpaRepository<Module, Integer>{
public List<Module> findAllByTimelineTimelineId(Integer timelineId);
}
IModuleService
public interface IModuleService {
public List<Module> findByTimelineId(Integer timelineId);
}
ModuleServiceImpl
public List<Module> findByTimelineId(Integer timelineId) {
// TODO Auto-generated method stub
return moduleRepo.findAllByTimelineTimelineId(timelineId);
}
Controller
#RequestMapping("/gettimeline/{timeline_id}")
public List<Module> findByTimelineId(#PathVariable Integer timeline_id){
return moduleService.findByTimelineId(timeline_id);
}
Now when I run this url in Postman: http://localhost:8083/gettimeline/1
I get an infinite loop, I am unable to decode the error, also is there any problem with OneToMany mapping, I am new to JPA:
[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[{"module_id":1,"module_name":"Sleep","duration":10,"timeline":{"timelineId":1,"module":[
Please help, thank you in advance :)
The infinite loop issue is caused by the one-to-many relation. There are several ways of fixing this, but I find view model classes like shown below as the cleanest approach.
Please note that the owning side of the one-to-many relation is not included in the code below, only the many-to-one. This can be done the other way around, but from your code, I guess this is what you want.
TimelineVM class
package no.mycompany.myapp.misc;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
public class TimelineVM {
private Integer timelineId;
public TimelineVM(Timeline timeline) {
this.timelineId = timeline.getTimelineId();
}
}
ModuleVM class
package no.mycompany.myapp.misc;
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
public class ModuleVM {
private Integer module_id;
private String module_name;
private Integer duration;
private TimelineVM timeline;
public ModuleVM(Module module) {
this.module_id = module.getModule_id();
this.module_name = module.getModule_name();
this.duration = module.getDuration();
this.timeline = new TimelineVM(module.getTimeline());
}
}
Controller method
#RequestMapping("/gettimeline/{timeline_id}")
public List<ModuleVM> findByTimelineId(#PathVariable Integer timeline_id){
return moduleService.findByTimelineId(timeline_id).stream().map(ModuleVM::new).collect(Collectors.toList());
}
I have a problem with JPA inheritance. The database model is also specially built. It contains several tables with the same attributes (the tables were intentionally cut by country) and all these tables connect to another table (OneToOne).
Here is an example of the data model:
usa_user, germany_user, austria_user. All these tables have the same attributes (id, name, address). Now the address was also built up according to the countries e.g. usa_address, germany_address, austria_address.
Now I don't know or have the problem that I have been mapping them correctly for a long time. I have the following:
// All Lombok Getter, Setter Args,...
#MappedSuperclass
public abstract Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", referencedColumnName = "id")
#JsonIgnore
private User user;
private String name;
private String addr_num;
...
}
// All Lombok Getter, Setter Args,...
#MappedSuperclass
public abstract User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JsonIgnore
private Address address;
private String name;
}
#Entity
#Table(name = "usa_user")
public class UsaUser extends User {}
#Entity
#Table(name = "austria_user")
public class AustriaUser extends User {}
#Entity
#Table(name = "germany_user")
public class GermanyUser extends User {}
#Entity
#Table(name = "usa_address")
public class UsaAddress extends Address {}
#Entity
#Table(name = "austria_address")
public class AustriaAddress extends Address {}
#Entity
#Table(name = "germany_address")
public class GermanyAddress extends Address {}
But unfortunately this does not work. Every time I start it JPA notices that it can't map the Entities Address - User (which is understandable because they are not entities but abstract classes). What would be the best way to solve this? I want to avoid that I have to list the attributes in all these entities because it would be redundant.
The goal is to find out how I can use a #MappedSuperclass in a #MappedSuperclass.
MappedSuperclass is not queryable and thus also not joinable. You need to map this as an abstract entity with the table per class inheritance strategy. Just switch to #Entity on the Address and User and add #Inheritance(TABLE_PER_CLASS).
I'm writing 3 tables in the following relation:
Club class:
#Setter
#Getter
#Entity
#Table(name = "Club")
public class Club {
#Id
#GeneratedValue
private Long id;
private String name;
private String type;
private String mainPage;
private String logo;
#OneToMany(mappedBy="clubProductKey.club", cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "clubProductKey.club", allowSetters=true)
private Set<ClubProduct> clubProducts;
...
Product class:
#Setter
#Getter
#Entity
#Table(name = "Product")
public class Product {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy="clubProductKey.product", cascade = CascadeType.ALL)
#JsonIgnoreProperties(value = "clubProductKey.product", allowSetters=true)
private Set<ClubProduct> clubProducts;
...
ClubProduct class:
#Setter
#Getter
#Entity
#Table(name = "ClubProduct")
public class ClubProduct {
#EmbeddedId
private ClubProductKey clubProductKey;
...
ClubProductKey class:
#Setter
#Getter
#Embeddable
public class ClubProductKey implements Serializable {
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH })
#JoinColumn(name = "club_id", referencedColumnName = "id")
#JsonIgnoreProperties(value = "clubProducts", allowSetters=true)
private Club club;
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.REFRESH })
#JoinColumn(name = "product_id", referencedColumnName = "id")
#JsonIgnoreProperties(value = "clubProducts", allowSetters=true)
private Product product;
...
ClubProductRepository class:
public interface ClubProductRepository extends JpaRepository<ClubProduct, ClubProductKey> {
public List<ClubProduct> findByClubProductKeyClub(Club club);
public List<ClubProduct> findByClubProductKeyProduct(Product product);
}
I try to save clubProduct like this:
#Service
public class ClubProductServiceImp implements ClubProductService {
#Autowired
private ClubProductRepository clubProductRepository;
...
ClubProduct savedClubProduct = clubProductRepository.save(clubProduct);
return savedClubProduct;
}
However I find that the clubProduct is not saved in the clubProducts list in the club or product entity, the list is null. Must I add lines like club.getClubProducts.add(clubProduct) or is there any other way to make it added automatically?
Thank you.
The #OnetoMany mapping in your Club class uses the attribute mappedby which means that it represents the owning side of the relation responsible for handling the mapping. However, we still need to have both sides in sync as otherwise, we break the Domain Model relationship consistency, and the entity state transitions are not guaranteed to work unless both sides are properly synchronized.
The answer is yes, you have to manage the java relations yourself so that the clubProducts gets persisted. You are using an instance of the repository class club to persist the data so , you should add a setter method like :
public void addClubProduct(ClubProduct clubProduct) {
if (clubProduct!= null) {
if (clubProduct== null) {
clubProduct= new ArrayList<ClubProduct>();
}
clubProducts.add(clubProduct);
clubProduct.setClubProduct(this);
}
}
also a method to remove it from the list and use these method in your code to set the values to the list properly before initiating save . Read related article
I'm trying to add a order with equipment list, here's my entities:
the order entity
#Entity #Table(name = "orders") public class Order extends Ticket{
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
the equipment entity
#Entity #Table(name = "equipments") public class Equipment extends DateAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 30)
private String name;
#NotNull
private Long nbr_piece ;
#OneToMany(mappedBy = "equipment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
and the order_equipment entity
#Entity #Table(name = "order_equipment") public class OrderEquipment extends DateAudit { #Id
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#Id
#ManyToOne
#JoinColumn(name = "equipment_id")
private Equipment equipment;
#NotBlank
#Column(name = "quantity")
private Long quantity;}
here is the add function in the orderController
#PostMapping("/orders")
public Order createOrder(#Valid #RequestBody Order Order){
Order.setObservateurEmail(Order.getObservateurEmail());
Order.setObject(Order.getObject());
Order.setDescription(Order.getDescription());
return orderRepository.save(Order);
}
I have seen a mistakes there, lemme try to help you. Since you issue is not clear, please lemme know if it does/does not work:
You have two bidirectional mappings there:
Order (with ALL cascade) <-> OrderEquipment
Equipment (with ALL cascade) <-> OrderEquipment
You are using #JoinColumn for both of them, even though they are bidirectional. Please take a look at this. You should always use the mappedBy attribute when defining bidirectional relationships.
Now, you are receiving an Order object from a POST request, making changes to 3 attributes and then saving it. Since the mapping between the Order and OrderEquipment have the CascadeType.ALL attribute, any save on the Order object will save all OrderEquipment children associated. If the Order object you are receiving already have OrderEquipment children, your method will also save/update them.
Your POST mapping looks good to me, just take care with your table relationship definitions.
Take a look at this answer to check how a lits of entities should be formatted on a JSON POST.