I will try create #ManyToMany relation in my SpringBoot application.
I use JPARespository, I create 2 models:
User
#Entity(name = "User")
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#ManyToMany//(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#LazyCollection(value = LazyCollectionOption.FALSE)
#JoinTable(
name = "users_groups",
joinColumns = #JoinColumn(name = "users_id"),
inverseJoinColumns = #JoinColumn(name = "groups_id")
)
private Set<UGroup> groups;
public User() {
this.groups = new HashSet<>();
}
public void addGroup(UGroup uGroup) {
this.groups.add(uGroup);
uGroup.users.add(this);
}
public void removeGroup(UGroup uGroup) {
this.groups.remove(uGroup);
uGroup.users.remove(this);
}
}
UGroups
#Data
#Entity(name = "UGroup")
#Table(name = "groups")
public class UGroup {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#ManyToMany(mappedBy = "groups", cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#LazyCollection(value = LazyCollectionOption.FALSE)
public Set<User> users;
public UGroup() {
this.users = new HashSet<>();
}
public void addUser(User user) {
this.users.add(user);
user.getGroups().add(this);
}
public void removeUser(User user) {
this.users.remove(user);
user.getGroups().remove(this);
}
}
I load start data via DataConfiguration class:
#Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
User user = new User();
user.setName("Damian");
userRepository.save(user);
user = new User();
user.setName("Marta");
userRepository.save(user);
user = new User();
user.setName("Natalia");
userRepository.save(user);
UGroup uGroup = new UGroup();
uGroup.setName("Mieszkanie");
uGroup = uGroupRepository.save(uGroup);
user = userRepository.findById(1).get();
user.addGroup(uGroup);
user = userRepository.save(user);
// unable to execute
/*
user = userRepository.findById(2).get();
user.addGroup(uGroup);
user = userRepository.save(user);
*/
// unable to execute 2
/*
uGroup = uGroupRepository.findById(4).get();
uGroup.addUser(userRepository.findById(3).get());
uGroup = uGroupRepository.save(uGroup);
*/
}
When i execute only insert group for 1 user all is good.
Although when I try execute insert group for 1 and 2 users or insert 3 user for group it cause java.lang.StackOverflowError: null exception.
I don know where is error. Can anyone help me and explain me where and why I should change something?
Thanks in advance.
With Join Table,it it possible to map ManyToMany bidirectional relation.These are the modified entity classes
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity(name = "User")
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Set<UGroup> getGroups() {
return groups;
}
public void setGroups(Set<UGroup> groups) {
this.groups = groups;
}
public void setName(String name) {
this.name = name;
}
#ManyToMany(mappedBy = "users",cascade = CascadeType.ALL)
#LazyCollection(value = LazyCollectionOption.FALSE)
private Set<UGroup> groups=new HashSet<UGroup>();
public User() {
}
public void addGroup(UGroup uGroup) {
this.groups.add(uGroup);
uGroup.users.add(this);
}
public void removeGroup(UGroup uGroup) {
this.groups.remove(uGroup);
uGroup.users.remove(this);
}
}
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
#Entity(name = "UGroup")
#Table(name = "groups")
public class UGroup {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "USER_GROUP_MAP" ,joinColumns = #JoinColumn(name="GROUP_ID",referencedColumnName = "ID")
,inverseJoinColumns = #JoinColumn(name="USER_ID",referencedColumnName = "ID"))
#LazyCollection(value = LazyCollectionOption.FALSE)
public Set<User> users=new HashSet<>();
public UGroup() {
}
public void addUser(User user) {
this.users.add(user);
user.addGroup(this);
}
public void removeUser(User user) {
this.users.remove(user);
user.removeGroup(this);
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
For ManyToMany relations join table is necessary.
SELECT * FROM USER_GROUP_MAP;
GROUP_ID USER_ID
4 1
4 2
4 3
This is the result with the code you have posted.You should override hashcode and equals method in both the entity classes in order to avoid duplication of data in the tables.
Code used for testing the above relationship mappings.
#Override
public void onApplicationEvent(ContextRefreshedEvent applicationEvent) {
User user = new User();
user.setName("Damian");
userRepository.save(user);
user = new User();
user.setName("Marta");
userRepository.save(user);
user = new User();
user.setName("Natalia");
userRepository.save(user);
UGroup uGroup = new UGroup();
uGroup.setName("Mieszkanie");
uGroup = uGroupRepository.save(uGroup);
user = userRepository.findById(1).get();
user.addGroup(uGroup);
user = userRepository.save(user);
// unable to execute
user = userRepository.findById(2).get();
user.addGroup(uGroupRepository.findById(4).get());
user = userRepository.save(user);
// unable to execute 2
uGroup = uGroupRepository.findById(4).get();
uGroup.addUser(userRepository.findById(3).get());
uGroup = uGroupRepository.save(uGroup);
}
Related
I'm writing a site of a hospital. This is an MVC application with database. Database contains data about patients, doctors etc.
I need to get List of doctors which should be sorted by patient count. I have already tried to do this with Comparator inside Java code like this example:
Page<Doctor> pageDoctor = doctorRepository.findAll(pageable);
List<Doctor> doctorList = pageDoctor.getContent();
doctorList.sort(Comparator.comparing(o -> patientRepository.findAllByDoctor(o).size()));
but I need the sorted list inside Page content. I don't really understand how to make the query equivalent to this example, because I'm new to SQL. Here are my entity and repository classes.
Doctor.java
#Entity
#Table(name = "doctors")
public class Doctor {
#Id
#Column(name = "id", nullable = false, unique = true)
#SequenceGenerator(name="doctors_generator", sequenceName = "doctors_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "doctors_generator")
private Long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "doctors_type_id")
private DoctorsType doctorsType;
public Doctor(User user, DoctorsType doctorsType) {
this.user = user;
this.doctorsType = doctorsType;
}
public Doctor() {
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public DoctorsType getDoctorsType() {
return doctorsType;
}
public void setDoctorsType(DoctorsType doctorsType) {
this.doctorsType = doctorsType;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Patient.java
#Entity
#Table(name = "patients")
public class Patient {
#Id
#Column(name = "id", nullable = false, unique = true)
#SequenceGenerator(name="patients_generator", sequenceName = "patients_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "patients_generator")
private Long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "doctor_id")
private Doctor doctor;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "treatment_id")
private Treatment treatment;
public Patient(User user, Doctor doctor, Treatment treatment) {
this.user = user;
this.doctor = doctor;
this.treatment = treatment;
}
public Patient() {
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Doctor getDoctor() {
return doctor;
}
public void setDoctor(Doctor doctor) {
this.doctor = doctor;
}
public Treatment getTreatment() {
return treatment;
}
public void setTreatment(Treatment treatment) {
this.treatment = treatment;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
PatientRepository.java
#Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
Patient findPatientByUser(User user);
List<Patient> findAllByDoctor(Doctor doctor);
Patient findPatientById(long id);
Page<Patient> findAllByOrderByIdAsc(Pageable pageable);
List<Patient> findAllByOrderByIdAsc();
}
DoctorRepository.java
#Repository
public interface DoctorRepository extends JpaRepository<Doctor, Long> {
Doctor findDoctorById(long id);
Doctor findDoctorByUser(User user);
#Query(
//sql query there
)
Page<Doctor> findAllByPatientCountAsc(Pageable pageable);
Page<Doctor> findAll(Pageable pageable);
List<Doctor> findAllByOrderByIdAsc();
List<Doctor> findAllByDoctorsTypeNot(DoctorsType doctorsType);
List<Doctor> findAllByDoctorsType(DoctorsType doctorsType);
}
Thanks for your answers in advance.
Check this one:
SELECT d FROM Doctor d,
LEFT JOIN Patient p ON d.id = p.doctor.id
GROUP BY d
ORDER BY COUNT(p.id)
I have a small problem with hibernate query with one-to-one relation
I created 3 entities. In case of one-to-one relation user-to-group it works properly. I use property create in hiberanate config and it creates correct table with correct FK.
Creating record work correct too
MyUser user = new MyUser();
user.setLogin("Mark");
user.setPassword("123456");
MyPresence presence = new MyPresence();
presence.setPresenceId(2);
presence.setUser(user);
session.beginTransaction();
session.save(user);
session.save(presence);
session.getTransaction().commit();
session.close();
if get User by presence it works correct
int id = 2;
MyPresence myPresence = session.load(MyPresence.class, id);
System.out.println(myPresence);
But when I'm try to get presence by user result from database hibernate build wrong sql query
int id = 3;
MyUser myUser = session.load(MyUser.class, id);
System.out.println(myUser);
Generated Hibernate sql:
Hibernate: select myuser0_.id as id1_2_0_, myuser0_.group_id as group_id4_2_0_, myuser0_.login as login2_2_0_, myuser0_.password as password3_2_0_, mypresence1_.id as id1_1_1_, mypresence1_.presence_id as presence2_1_1_, mypresence1_.updated_at as updated_3_1_1_, mypresence1_.user_id as user_id4_1_1_ from users myuser0_ left outer join user_presence mypresence1_ on myuser0_.id=mypresence1_.id where myuser0_.id=?
But I expect to see
on myuser0_.id=mypresence1_.user_id instead of on myuser0_.id=mypresence1_.id
MyUser.java
#Entity
#Table(name = "users")
public class MyUser {
private int id;
private String login;
private String password;
private MyGroup group;
private MyPresence presence;
public MyUser() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column(name = "login")
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
#Column(name = "password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#JoinColumn(name = "group_id")
#OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
public MyGroup getGroup() {
return group;
}
public void setGroup(MyGroup group) {
this.group = group;
}
#OneToOne(cascade = CascadeType.DETACH)
#JoinColumn(name = "id", referencedColumnName = "user_id")
public MyPresence getPresence() {
return presence;
}
public void setPresence(MyPresence presence) {
this.presence = presence;
}
}
MyPresence.java
#Entity
#Table(name = "presence")
public class MyPresence {
private int id;
private MyUser user;
private int presenceId;
private Date updatedAt;
public MyPresence() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "id")
public MyUser getUser() {
return user;
}
public void setUser(MyUser user) {
this.user = user;
}
#Column(name = "presence_id")
public int getPresenceId() {
return presenceId;
}
public void setPresenceId(int presenceId) {
this.presenceId = presenceId;
}
#Column(name = "updated_at")
public Date getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Date updatedAt) {
this.updatedAt = updatedAt;
}
I've got simple application in Spring and I want to implement method which set role when i am creating new user. So Its my code. How should I give Set roles to last method?
Role.java
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
#NaturalId
private RoleName name;
public Role(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public RoleName getName() {
return name;
}
public void setName(RoleName name) {
this.name = name;
}
}
RoleName enum
public enum RoleName {
ROLE_USER,
ROLE_ADMIN
}
Setter method in User.java
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
And how should I fill this method?
public User addUser(User user) {
Role userRole = roleRepository.findRoleByName(RoleName.ROLE_USER);
User newUser = new User();
newUser.setEmail(user.getEmail());
newUser.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
newUser.setUsername(user.getUsername());
newUser.setFirstName(user.getFirstName());
newUser.setLastName(user.getLastName());
newUser.setRoles();
return userRepository.save(newUser);
}
I don't know what should i put in newUser.setRoles()
you can use Stream of java 8 as bellow:
Set<Role> roles = Stream.of(userRole)
.collect(Collectors.toCollection(HashSet::new));
newUser.setRoles(roles);
I'm implemented this solution for many to many with exstra fields : Many to Many Hibernate Mapping for additional property in the join table
My code:
#Entity
public class User {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user", cascade = CascadeType.ALL)
private Set<UserRole> userRoles;
}
#Entity
public class Role {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.role", cascade = CascadeType.ALL)
private Set<UserRole> userRoles;
}
#Entity
#AssociationOverrides({ #AssociationOverride(name = "pk.user", joinColumns = #JoinColumn(name = "user_id")),
#AssociationOverride(name = "pk.role", joinColumns = #JoinColumn(name = "role_id")) })
public class UserRole{
private UserRoleId pk;
public UserRole(User user, Role role) {
super();
this.pk = new UserRoleId(
user, role);
}
public UserRole() {
super();
}
public Long getUserId() {
return this.pk.getUser().getId();
}
#EmbeddedId
public UserRoleId getPk() {
return pk;
}
public void setPk(UserRoleId pk) {
this.pk = pk;
}
public User getUser() {
return this.pk.getUser();
}
public Role getRole() {
return this.pk.getRole();
}
}
#SuppressWarnings("serial")
#Embeddable
public class UserRoleId implements Serializable {
private User user;
private Role role;
public UserRoleId() {
super();
}
public UserRoleId(User user, Role role) {
super();
this.user = user;
this.role = role;
}
#ManyToOne
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
I'm getting this error:
Caused by: org.hibernate.MappingException: Could not determine type for: com.xxx.model.entities.User, at table: UserRole, for columns: [org.hibernate.mapping.Column(user)]
I'm guessing it has something to do with the getUser function in UserRole entity.
I also meet the same question as you, and I solved it through moving the annotation to the front of corresponding get method.
This is most likely a very basic question, but nevertheless:
Basically the User entity has an Id and a privilege enum.
The group has an id and a name.
I wonder how to model a relationship where an user can be in multiple groups, having different privilege levels in different groups. Every group can of course have multiple members.
What's the way I'm supposed to solve that idiomatically with Hibernate?
Adding some membership field to User? A membership field to Group? Both? Creating a new class for it? Which annotations are required to wire these things together?
I used next architecture
UserEntity class
#Entity
#Table(name = "user")
public class UserEntity implements Serializable {
private Long user_id;
private String username;
private String password;
public UserEntity() {
}
public UserEntity(String name, String passwd) {
username = name;
password = passwd;
}
#Column(name = "password", nullable = false)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getUser_id() {
return user_id;
}
public void setUser_id(Long user_id) {
this.user_id = user_id;
}
#Column(name = "username", nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
AuthorityEntity class
#Entity
#Table(name = "authority_role")
public class AuthorityEntity implements Serializable {
private Integer id;
private String authority;
private List<UserEntity> people;
public AuthorityEntity() {
}
public AuthorityEntity(String authString) {
authority = authString;
}
#Column(name = "authority", nullable = false)
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToMany(targetEntity = UserEntity.class,
cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "authority_role_people",
joinColumns =
#JoinColumn(name = "authority_role_id"),
inverseJoinColumns =
#JoinColumn(name = "user_id"))
public List<UserEntity> getPeople() {
return people;
}
public void setPeople(List<UserEntity> people) {
this.people = people;
}
}
In fact - this is how implemented spring security plugin.
In your case you can implement that architecture, which will be more useful for you.