Application of ManyToMany - Spring does not work - spring

I am implementing many to many relationship between Users and Permissions and I cannot conclude where am I wrong, it is not working (Unable to map collection rs.raf.demo.model.User.permissions):
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column
private String username;
#Column
private String password;
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "ID")
)
private List<Permission> permissions = new ArrayList<>();
}
=================================================================================
#Data
#Entity
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "permission_id")
private Long userId;
private String type;
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID")
)
#JsonIgnore
private List<User> users = new ArrayList<>();
}
I implemented Users and Permissions with looking in this example, this is working (and I cannot see difference with Users and Permission):
#Data
#Entity
#Table(name = "STUD")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String lastName;
private String firstName;
#ManyToMany
#JoinTable(
name = "STUDENTS_COURSES",
joinColumns = #JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "COURSE_ID", referencedColumnName = "ID")
)
private List<Course> courses = new ArrayList<>();
}
================================================================================
#Data
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
#ManyToMany
#JoinTable(
name = "STUDENTS_COURSES",
joinColumns = #JoinColumn(name = "COURSE_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "STUDENT_ID", referencedColumnName = "ID")
)
#JsonIgnore
private List<Student> students = new ArrayList<>();
public void addStudent(Student student) {
students.add(student);
student.getCourses().add(this);
}
public void removeStudent(Student student) {
students.remove(student);
student.getCourses().remove(this);
}
}

JPA uses (and creates in your case probably) intermediate join table for many to many relationship and you cannot use different reference column name from name you specified in entity.
So you need to set:
in User class
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "PERMISSION_ID")
)
in Permission
#ManyToMany
#JoinTable(
name = "USERS_PERMISSIONS",
joinColumns = #JoinColumn(name = "PERMISSION_ID", referencedColumnName = "PERMISSION_ID"),
inverseJoinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")
)
And same for other tables.

Related

Springboot ExampleMatcher always returns no result

I have the following entity and I'm trying to use ExampleMatcher for simple queries:
#Entity(name="UserAccount")
#Table(name = "useraccount", catalog = "useraccount")
#Data
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false, unique = true)
private String mail;
private String password;
private boolean isEnabled;
private Timestamp credentialExpire;
private boolean isAccountNonLocked;
private boolean isSuspended;
private Timestamp accountExpire;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#JoinTable(name = "user_to_privileges", catalog = "useraccount",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "privilege_id", referencedColumnName = "id", nullable = false)})
private Set<Privilege> privileges= new HashSet<>();
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#JoinTable(name = "user_to_organizations", catalog = "useraccount",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "organization_id", referencedColumnName = "id", nullable = false)})
private Set<Organization> organizations= new HashSet<>();
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
#Setter(AccessLevel.NONE)
private UserRegister register;
#OneToMany(mappedBy ="tokenId.user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
private Set<SecureToken> tokens = new HashSet<>();
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
private UserLogin login;
//all the methods omitted from brevity
}
I create the Example matcher as follows:
UserAccount account= new UserAccount();
account.setUsername("John");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
Example<UserAccount> regExample = Example.of(account, matcher);
List<UserAccount> out = repository.findAll(regExample);
Consider that a user with username "John" exists, but the output is always empty, no matter what parameter I fill.
Edit: this helps to find the solution: Are there any possible ways to ignore all paths of JPA Example Matcher. There is no way to automatically ignore primitive fields when not used?
Edit: Notice that I want to find all the UserAccount containing the specified strings in the selected fields. With other entities the configuration of ExampleMatcher works.

OSIV:false , cannot get session for resolving FetchType.Lazy items, even in the scope of #Transactional

In my spring application, I have disabled osiv (spring.jpa.open-in-view=false) to avoid holding hibernate sessions throughout a request lifecycle.
After disabling OSIV, I am getting the following error when I try to access a field in my entity that has the FetchType set as LAZY
org.hibernate.LazyInitializationException: could not initialize proxy [sg.identity.auth.entity.OrganizationEntity#2] - no Session
This is the entity I'm using and I'm trying to access userEntity.getOrganization() which results in the LazyInitializationException.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "\"USER\"")
public class UserEntity extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "organization_user",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "organization_id", referencedColumnName = "organization_id"))
private OrganizationEntity organization;
#ManyToMany(cascade = CascadeType.MERGE)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "user_parent_organization_unit_mapping",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "organization_unit_id", referencedColumnName = "organization_unit_id"))
private Set<OrganizationUnitEntity> organizationUnits;
#OneToOne(cascade = CascadeType.ALL)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "user_exclusion_mapping",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "excluding_permission_set_id", referencedColumnName = "permission_set_id"))
private PermissionSetEntity exclusionPermissions;
#ManyToMany(cascade = CascadeType.MERGE)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "role_id"))
private Set<RoleEntity> roles;
}
I'm calling the user.getOrganizationEntity in a method annotated with #Transactional so It should receive an open session to resolve the lazy items. But in my case, it is not working
#Transactional
public Organization getOrganization(String emailId) {
return userRepository.getUserByEmailId(emailId)
.map(UserEntity::getOrganization)
.map(OrganizationMapper::toDto)
.orElseThrow(() -> new IllegalArgumentException("error"));
}
The user.getOrganization is resolving correctly when I try using TransactionTemplate.
#Transactional
public Organization getOrganization(String emailId) {
return transactionTemplate.execute(status -> {
return userRepository.getUserByEmailId(emailId)
.map(UserEntity::getOrganization)
.map(OrganizationMapper::toDto)
.orElseThrow(() -> new IllegalArgumentException("error"));
});
}
I got it working with #Transactional itself by setting the property spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true, but from my research, i found that it is not recommended to set this flag as true

How to send POST request with Many to many relationship in Spring Boot

Anyone have any ideas on how I could do postmapping for the Many-to-Many relationship? Getting the data works, but this is what I'm having trouble with
I tried using the "guide" but unfortunately I don't understand it very well yet
Here is my entities:
Album
#Entity
public class Album implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;
String name;
String artist;
String cover;
#ManyToMany(fetch = FetchType.LAZY, cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
})
#JoinTable( name = "user_albums",
joinColumns = #JoinColumn(name = "album_id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "user_id", nullable = false)
)
#JsonBackReference
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<User> users = new HashSet<>();
User:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 20)
private String username;
#NotBlank
#Size(max = 50)
#Email
private String email;
#NotBlank
#Size(max = 120)
private String password;
#ManyToMany(fetch = FetchType.LAZY, cascade =
{
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.PERSIST
})
#JoinTable( name = "user_albums",
joinColumns = #JoinColumn(name = "user_id", nullable = false),
inverseJoinColumns = #JoinColumn(name = "album_id", nullable = false)
)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<Album> albums = new HashSet<>();
I tried it this way but it didn't work
#PostMapping("/users/mal/{userId}/album")
public ResponseEntity<Album> addAlbum(#PathVariable(value = "userId") Long userId, #RequestBody Album albumRequest, User userRequest) {
Album newMal = userRepo.findById(userId).map(user -> {
long albumId = userRequest.getId();
user.addAlbum(albumRequest);
return albumRepo.save(albumRequest);
}).orElseThrow(() -> new RuntimeException("Not found USER with id = " + userId));
return new ResponseEntity<>(newMal, HttpStatus.CREATED);

Delete just one side of a manytomany relationship Hibernate

I have two tables that have a manytomany relationship:
first one is ad ( represents all the products)
#Entity
#Table(name = "ad")
public class Ad {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "admin_id")
private Admin admin;
#ManyToMany(mappedBy = "ads", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
Second one is order:
#Entity
#Table(name = "`order`")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne( cascade=CascadeType.
#JoinColumn(name = "buyer_id")
private Buyer buyer;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "order_ad", joinColumns = {
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "ad_id", referencedColumnName = "id", nullable = false, updatable = false) })
private List<Ad> ads = new ArrayList<>();
when I delete order using its repository that is representing a cancellation so I don't want the ads to be deleted as well.
How can I do that?
PS: I can't find a replacement for the orphanRemoval of the onetomany relationship

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