ManyToMany relation use in service - spring-boot

Job entity
#Column(name = "name")
private String name;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "job_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private List<User> user;
User entity
#Column(name = "email")
private String email;
#ManyToMany
#JoinTable(name = "user_job",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles;
Role entity
#Column(name = "name")
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
Here we have a table user_job with 3 ids and I want to insert data in service layer. How I can do it and what repository I should implement or use existent like user/role/job?
class UserJobService{
public void setUserJob(User user, Job job, Role role){
}
}

The problem with #ManyToMany association is you can't delete a record directly from user_job table, using Hibernate. To delete the record, you need to load a user with all his jobs. So better to add UserJobEntity
#Entity
#Table(name = "USER_JOBS")
class UserJobEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "JOB_ID")
private Job job;
}
public UserJobEntity createUserJob(UserEntity user, JobEntity job) {
UserJobEntity userJob = new UserJobEntity();
userJob.setUser(user);
userJob.setJob(job);
return userJobRepository.save(userJob);
}
Probably you will want to add (user, job) unique constraint to user_jobs table.
Some advices
Use plurals for table names. user_jobs in place of user_job
Role is tabular data. So it shouldn't have a users List.
Don't use Set for associated collections. Definitely you will encounter "multiple bugs fetch exception" and this exception will help you to change queries. With Set you can have large cross products and even don't notice them.

Related

JPA composite key spring boot

I made 2 one-to-one relationships with 2 foreign keys, but I can't make a constraint or how could I make the 2 unique keys be a unique key in the new table?
#Table(name = "cart")
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
private int quantity;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "product_id")
private Product products;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User users;
}
#Entity
#Table(name = "Products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private int quantity;
private Double price;
private String image;
It spells my 2 keys correctly, for example
user_id = 2 , product_id =2 would be ok
but if I enter another user_id=2, product_id=2 should not work, but for me it is entered in the database
You can use #PrimaryKeyJoinColumn in the #OneToOne relation:
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name = "product_id", referencedColumnName = "id")
private Product products;

JPA onetomany mapping showing nested data many times

I have two table user(id,name) and user_mails(id,email,user_id) user to user_mails have one to many relation.
I have created following entity in spring boot
User
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private Set<UserMail> userMails =new HashSet<UserMail>(0);
//Getter setter and constructor
}
UserMail
#Entity
#Table(name = "user_mails")
public class UserMail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "email", nullable = false)
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id")
private User user;
It is showing following output on calling controller
[{"id":1,"name":"Ram","userMails":[{"id":2,"name":"ram#b.com","user":{"id":1,"name":"Ram","userMails":[{"id":2,"name":"ram#b.com","user":{"id":1,"name":"Ram","userMails":[{"id":2,"name":"ram#b.com","user":{"id":1,"name":"Ram","userMails":[{"id":2,"name":"ram#b.com","user":{"id":1,"name":"Ram","userMails":[{"id":2,"name":"ram#b.com","user":{"id":1,"name":"Ram","userMails":[{"id":2,"name":"ram#b.com","user":{"id":1,"name":"Ram","userMails":
and more
I want to access all users with all mail ids also want to acces mail id with user details
What changes should I do to get proper result

I seem to need both #OneToOne and #OneToMany relationship

I'm writing an app to operate a collection of hardware that is supposed to be setup and shutdown repeatedly in different locations.
I want to track those installations so I've created separate entities for a physical object itself and the installation.
Station object needs to keep track of up to one (null while not installed) active installation information (so #OneToOne) but also all the previous installations (so #OneToMany)
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "stationInstallation", cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station_id", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
The other entity will have the station id, location and date of the setup as well as the shutdownDateTime being null.
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy ="stationInstallation", cascade - CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
Finally the shutdown method is supposed to unattach the entity from the Station entity by setting activeStationInstallation to null and setting shutdownDateTime to LocalDateTime.NOW.
...
stationInstallation.getStation().setActiveStationInstallation(null);
stationInstallation.setShotdownDateTime(LocalDateTime.NOW);
...
But that will obviously result in a growing number of StationInstallation "finished" objects, which would seem to require #ManyToOne relation with the Station all the while I want to keep #OneToOne relation with the activeStationInstallation.
What do?
I figured I could just make another entity calling it FinishedStationInstallation, remove shutdownDateTime from StationInstallation, make both immutable and instead of adding shutdownDateTime with a setter add it in the constructor while deleting the active version. And immutablity is an asset, ut at the same time I'd have to add several new tables to the db and keep track of and query two entities instead of one.
EDIT: I guess I could get rid of List stationInstallations from the 1st entity, since I don't really need to keep the track of it beyond db queries, but I added it for the clarity of the question.
I would model it this way:
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(mappedBy ="stationInstallation", cascade = CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
The one-to-one relation from Station to installation has a FK => so no mapped by
The one-to-many relation from Station to installation is mapped by the station association on installation, which obviously has to be a many-to-one association, since the other side is a one-to-many

OneToOne JPA issue

I have 2 class
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String age;
#OneToOne
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
}
and
public class Address {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String building;
private String country;
#OneToOne(mappedBy = "address")
private User user;
}
in my table address, I have a few rows.
When I insert table user with data
{
"id":null,
"name":"Foo",
"age":"18",
"address":{
"id":1,
"building":"Too",
"country":"ABS"
}
}
Table user have 1 row with address_id =1.
I insert same data as above
Table user have 2 row with address_id =1.
My answer is: why 2 table connected by one to one can happen the above case?
You can find your answer here
Why #OneToOne is allowing duplicate associations?
Basically, #JoinColumn(name = "address_id", referencedColumnName = "id") alone doesn't serve the semantics of one-to-one in the database, you need to add unique=true into the #JoinColumn, which makes it #JoinColumn(name = "address_id", referencedColumnName = "id", unique = true).
Side-note: I suggest you drop your tables and then re-creating them before trying this out. If you are using Hibernate, you can set hibernate.hbm2ddl.auto to create-drop

Foreign key must have same number of columns as the referenced primary key for manyToMany

here job has a set of employees, and employee have a set of jobs, but Spring give me this exception
Caused by: org.hibernate.MappingException: Foreign key
(FK1kec5bwba2rl0j8garlarwe3d:account [employee_id])) must have same
number of columns as the referenced primary key (employee
[job_id,employee_id])
this is my employee class :
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id ;
private String firstname ;
private String lastname ;
private String email ;
private String phone ;
private String address ;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "account_id")
private Account account ;
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
#ManyToOne
#JoinColumn(name = "departement_id")
#JsonIgnore
private Departement departement ;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "jobs", joinColumns = #JoinColumn(name = "employee_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "job_id", referencedColumnName = "id"))
private Set<Job>jobs ;
....
}
and here is my job class :
#Entity
#Inheritance
public abstract class Job {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id ;
private String status ; // (pending or done)
private Date date ;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "employee", joinColumns = #JoinColumn(name = "job_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "employee_id", referencedColumnName = "id"))
private Set<Employee> employee ;
#ManyToOne
#JoinColumn(name = "BusinnesPartner_id")
#JsonIgnore
private BusineesPartner busineesPartner ;
}
Please can you explain to me why I get this exception.
#JoinTable annotation should be used in the owning entity side , in the other side you should not have #JoinTable , you need to have mappedBy to define the reverse relation since you are establishing a bidirectional relation is if the Job is the owning entity you need to modify Employee pojo
#ManyToMany(cascade = CascadeType.ALL, mappedBy="employee")
private Set<Job>jobs ;

Resources