Spring boot entities,relationships and repository confusion - spring-boot

I am confused as to how relationships work in entities and what this means with regard to my JPA repository.
I have a class called Loan which stores a list of albums for each loan.
I have a loan repository and an album repository. The album repository is filled with albums when I start the application. The albumId is autogenerated.
When I create a new loan and try to add an album from the repository I get an exception :
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.library.demo.entity.Album; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.library.demo.entity.Album
If I create a new loan and add a new album on the fly then it works as expected. During the debug I realised that this is because albumId is null when adding a new album on the fly to the loan, presumably because it adds the album to the repository and generates a new albumId when the loan is created.
My album entity looks like this :
#Entity
public class Album implements Serializable {
private static final long serialVersionUID = 0x63A6DA99AA12AAA8L;
#Column #GeneratedValue(strategy = GenerationType.AUTO) #Id private Integer albumId;
#Column (unique=true) private String barcode;
#Column private String band;
#Column private String title;
#Column private String genre;
#Column private Integer year;
#Column private String artworkFilename;
#Column private Boolean enabled;
#Column private Boolean isLoanable;
#Column private Integer numberOfCopies;
#ManyToOne
private Loan loan;
And my loan looks like this :
public class Loan implements Serializable {
private static final long serialVersionUID = 0x62B6DA99AA12AAA8L;
public void setLoanId(Integer loanId) {
this.loanId = loanId;
}
#Column #GeneratedValue(strategy = GenerationType.AUTO) #Id private Integer loanId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Album> albums= new ArrayList<>();
#Column private Integer customerId;
#Column private Date dateLoaned;
#Column private Date dateToReturn;
#Column private Boolean expired;
I am also confused as to why the album has to refer back to the loan with a ManyToOne annotation. Why does the album have to refer to the loan?
I am mostly used to relation databases so maybe I am thinking about things in the wrong way. If I can only add new albums to the loan then it defeats the purpose of what I am trying to do.

Loan and Album tables have a one-to-many relationship.
So in Album your mapping should be like below:
#ManyToOne
#JoinColumn(name = "loan_id")
private Loan loan;
Considering loan_id is the primary key of Loan.
And in Loan your mapping should be like below:
#OneToMany(mappedBy = "loan", cascade = CascadeType.ALL)
private List<Album> albums;
#OneToMany and #ManyToOne defines a one-to-many and many-to-one relationship between 2 entities. #JoinColumn indicates the entity is the owner of the relationship: the corresponding table has a column with a foreign key to the referenced table. mappedBy indicates the entity is the inverse of the relationship

I have updated my source as you have described and made some changes to the start up.
If I create the loan first and then save it to the loan repository and add the album set afterwards, it no longer crashes.
Loan loan = new Loan(1, new Date(), calendar.getTime(),null);
loanRepository.save(loan);
List<Album> albumList = AlbumImport.getAlbumList();
albumRepository.save(albumList);
List<Album> albums = new ArrayList<>();
albums.add(albumList.get(1));
albums.add(albumList.get(5));
loan.setAlbums(albums);
However, when I run my getLoan test the album list is empty

Removing cascade = CascadeType.ALL fixed my problem.

Related

Spring Boot JPA - OneToMany relationship and database structure

I wonder what database structure would be the best option in my case:
I have entity Questionnaire:
#Table(name = "questionnaire")
public class Questionnaire extends BaseEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "fieldStatus")
private List<QuestionnaireField > fieldStatusList;
}
#Table(name = "questionnaire_field")
public class QuestionnaireField extends BaseEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "questionnaire_id")
private Long questionnaireId;
#Column(name = "field_id")
private Long fieldId; //this is id related to the other table Field
#Column(name = "completed")
private boolean completed; //because I need some additional informations like completed I think I can't use ManyToMany between Questionnaire and Field
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
#JoinColumn(name = "questionnaire_id")
private Questionnaire questionnaire;
As you see each Questionnaire can have multiple QuestionnaireFields, BUT each QuestionnaireField is of type Field (hence I added private Long fieldId). Table Field can have 10.000 different fields.
Summary:
one questionnaire can have e.g. 10 Fields, the second one 20 another Fields etc. To store fields related to some particular Questionnaire I created QuestionnaireField table with 2 columns: private Long questionnaireId; and private Long fieldId; . The question is if it is a good approach? That are plain columns not related to any Foreign Key... I try to find the best solution to save Questionnaire with related QuestionnaireFields that are a subset of a big Field table...

spring data - how to make unique constraint with custom logic?

using spring data, I created User 1:N UserDog N:1 Dog relation. Both 1:N relations are unidirectional #ManyToOne with UserDog being the custom relation table.
User entity:
#Entity
public class User {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
Dog entity:
#Entity
public class Dog {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
User dog relation table:
#Entity
public class UserDog {
#Id
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private Dog dog;
#Column(nullable = false)
private Instant createdOn = Instant.now();
#Column
private Instant disabledOn;
}
Use case
Use case is to store history of User-Dog bindings, where the concrete Dog can be bound only to one User at the time. That's why I added createdOn and disabledOn columns to UserDog. disabledOn being null indicates that the relation is active and the Dog can't be assigned another User. If disabledOn is not null, then the record is stored only for evidence purposes and the Dog can be assigned to the same or another User again.
Question
How to ensure that the combination of Dog's id and disabledOn being null is unique in UserDog table?
In pseudo code I want something like this:
#Entity
#UniqueConstraint({#UniqueConstraint(this.dog.id), #NullConstraint(this.disabledOn)})
public class UserDog {...}
You can simply create a unique constraint for dogId and disabledOn.
It does add the limitation that no two relationships may end at the same time but this seems to fit your use case.

Spring hibernate JPA One to many mapping:Duplicate key error

I have one to many relationships between the following 2 entities.
#Entity
public class OrderItem {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name="ORDER_PRODUCT_ID")
private Product product;
private Long quantity;
private BigDecimal totalPrice;
#OneToMany
#JoinTable(name="orderItem_productaddition",joinColumns=#JoinColumn(name="orderItem_id"), inverseJoinColumns=#JoinColumn(name="ProductAddition_id"))
private Set<ProductAddition> listOfAdditions=new HashSet<>();
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(unique = true)
private ProductOption productOption;
}
Second Entity
#Entity
public class ProductAddition{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String additionDescription;
private BigDecimal additionPrice;
private BigDecimal additionsPriceForSmall;
private BigDecimal additionsPriceForNormal;
private BigDecimal additionsPriceForFamily;
private BigDecimal additionsPriceForParty;
#ManyToMany(fetch = FetchType.LAZY,mappedBy = "productAdditions")
#JsonBackReference
private Set<Product>product=new HashSet<>();
}
Following is the existing data in the join table order_item_productaddition.
For following insert i get the
Duplicate entry '41' for key 'UK_22djpp3b17x2x1vurh26crns'
insert into website_dev.order_item_productaddition values(379,41)
I do not understand what is wrong with my Join table mapping, it is one to many relationships and i expect multiple entries.
why does spring jpa adds unique key constraint to the join table.
Is there any way to make composite key constraint in Join table ? or how can i remove the constraint ?
Any advice will be highly appreciated.
Thanks.

Spring + hibernate one to one mapping

I am developing web application using spring and hibernate. I am using one to one mapping between two tables employee and PersonelDetails.
below are my bean classes
=======================Employee=====================================
#Entity
#Table(name="employee")
public class Employee {
#Id
#Column
#GeneratedValue
private int empid;
#Column
private String firstName;
#Column
private String lastName;
#Column
private String email;
#Column
private String password;
#Column
private boolean isAdmin;
#Column
private boolean isActive;
#Column
private boolean isLocked;
//getter setters
====================PersonalDetails class====================
#Entity
#Table(name="PersonalDetails")
public class PersonalDetails {
#Column
#Id
private int empid;
#Column
private String personalEmail;
#Column
private String mob;
#Column
private String permenantAdress;
#Column
private String currentAddress;
#Column
private String gender;
#Column
private String maritialStatus;
#MapsId
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "empid", referencedColumnName = "empid")
#ForeignKey(name="empid")
private Employee employee;
//getter setters
In my application table employee is filled by Admin user while creating new employee after that employyee himself fill personalDetails table by login to his accountCreated by Admin)
Now when I try to send personal details bean to hibernate layer first I have to get the employee bean from employee table then call setEmployee method over personalDetails class and save employee bean in personalDetails and send to hibernate layer for saving in database.
So while getting employee bean from database and again send back through personalDetails bean leads to a performance issue.
Can anyone help here to clarify while saving data in child table(PersonalDetails) is it really mandatory to pass parent object(Employee) ?
=======================code to store personalDetails===============
#RequestMapping(value="addpersonal")
public ModelAndView addPersonalDetails(#ModelAttribute("personalDetails") PersonalDetails personalDetails) {
//personalDetails.setEmpid(1);
personalDetails.setCurrentAddress("niljyoti");
personalDetails.setMob("9405715872");
personalDetails.setPermenantAdress("address");
Employee e = empService.getEmployeebyUserName(uname);
personalDetails.setEmployee(e);
personalDetailsService.addPersonalDetails(personalDetails);
return new ModelAndView("home");
}
On read:
You can change fetch strategy if you are worried.
Based od JPA spec, default fetch type for #OneToOne is EAGER.
By setting fetch = FetchType.LAZY, instead of real PersonalDetails object, an object of a subclass behaving as a proxy is returned. Hence, selecting from employee table starts only after getEmployee is called.
On write:
You need to specify connection between entities, in your model, the only way is the employee field. However, you can specify mappedBy, see answer to this question:
Java: Hibernate #OneToOne mapping

#OneToMany relationship issue while saving multiple values in Hibernate and Spring

I have two entities with #OneToMany bidirectional relationship as below:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer companyId;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="company")
Set<Employee> employees = new LinkedHashSet<>();
Employee class
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer empId;
private String name;
private String address;
private String email;
#ManyToOne
#JoinColumn(name="companyId")
Company company;
Intially i saved 2 employees as below:
Employee emp= new Employee();
emp.setName("John");
emp.setEmail("John#gmail.com");
employeeRepository.save(emp);
Employee emp2= new Employee();
emp2.setName("Smith");
emp2.setEmail("smith#gmail.com");
employeeRepository.save(emp2);
Now I want to save one employee working for 2 different companies like below:
Company company =new Company();
company.setName("Google");
Employee emp = employeeRepository.findOne(1);
company.getEmployees().add(emp);
emp.setCompany(company);
companyRepository.save(company);
Company company2 =new Company();
company2.setName("Microsoft");
company2.getEmployees().add(emp);
emp.setCompany(company2);
companyRepository.save(company2);
It is updating only second company id into employee table. I want both the companies to be assigned to that employee. How can I do that?
The issue here is that you have a one-to-many when obviously it should be a many-to-many, viz. an employee can work for more than one company and a company has many employees.
You can either change the relationship to a #ManyToMany (and use #JoinTable if required) or you can create another entity, say, CompanyEmployee to which both Employee and Company have a one-to-many-relationship. The latter approach is probably preferable as you can then record additional information about the association e.g. start_date, end-date etc.
#Entity
#Table(name = "company_employees")
public class CompanyEmployee {
#ManyToOne
private Employee employee;
#ManyToOne
private Company company;
private Date startDate;
private Date endDate;
}

Resources