Jpa not inserting the record in the join table for many to many relatioship - jpa

I have a Many-to-Many relationship with User and Role JPA entities. When I try to save the entities, both User and Role entities gets persisted in the table, but the junction table is not getting inserted with the records, Where am I going wrong
User Entity
#Entity
#Table(name="users")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "roles")
#ToString(exclude = "roles")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String password;
private double salary;
public User(String name, String password, double salary) {
super();
this.name = name;
this.password = password;
this.salary = salary;
}
#ManyToMany(
mappedBy = "users")
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
role.getUsers().add(this);
}
}
Role Entity
#Entity
#Table(name = "roles")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "users")
#ToString(exclude = "users")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String roleName;
public Role(String roleName) {
super();
this.roleName = roleName;
}
#ManyToMany
#JoinTable(
name="user_roles",
joinColumns = #JoinColumn(name="role_id", nullable = false),
inverseJoinColumns = #JoinColumn(name="user_id", nullable = false)
)
private Set<User> users = new HashSet<>();
}
Client class
#EventListener(ApplicationReadyEvent.class)
public void onApplicationStartup(ApplicationReadyEvent event) {
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
Role userRole = new Role("ROLE_USER");
Role adminRole = new Role("ROLE_ADMIN");
kiran.addRole(userRole);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(kiran);
this.userRepository.save(vinay);
}
Where am I going wrong?

You've mapped a bidirectional relationship, but are only setting one side of it in your object model - the wrong side. Should there ever be a discrepancy, the owning side controls the values of foreign keys, and since you have left the owning side empty, they aren't being set. You are responsible to set both sides of relationships and keeping them in synch with what you want in the database.
Since you don't have cascade options set on the relationships, you are also responsible for persisting the roles independently from the Users. Something more like:
public void onApplicationStartup(ApplicationReadyEvent event) {
// you might want to check to see if these roles already exists and use those instead of creating new ones
Role userRole = roleRepository.save(new Role("ROLE_USER"));
Role adminRole = roleRepository.save(new Role("ROLE_ADMIN"));
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
kiran.addRole(userRole);//assumes this adds the user to the role.users as well.
this.userRepository.save(kiran);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(vinay);
}
Also, you are using Set in your entities with Lombok using "#EqualsAndHashCode" generation. Don't do that!
Set uses the equals/hashcode logic to determine if two objects are the same to filter out duplicates, while Lombok generates those methods to use what are mutable fields. In the case you have new entities in those sets (ie this usecase), the IDs are null and will change when set from JPA. You are better off keeping Java equals/hashcode logic if you don't know what effects those will have on your application. try using either List in your model and/or not having Lombok generate those method for you.

Related

Is there a way to automatically avoid duplicate data insertion in SpringBoot with JPARepository and Postgres?

so my AppUser entity is like this :
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "Employee")
public class AppUser {
// public enum Roles {ADMIN,COLLABORATOR,PROJECT_MANAGER,MANAGER,ACCOUNTANT}
//TODO : add default values for fields that should not be null?
#Id #GeneratedValue(strategy = IDENTITY)
private Integer id;
private String first_name;
#Column(unique = true)
private String username;
private String password;
#Column(name="cnss")
private Boolean cnss;
#ManyToMany(fetch= FetchType.EAGER) // when we load a user, we load their roles
private Collection<Role> roles= new ArrayList<>();
}
in my main app i ran this code:
usserService.saveUser(new AppUser(null,"John Doe","Tom","1234",null,new ArrayList<>()));
userService.saveUser(new AppUser(null,"Michael","Michael","xyz",true,new ArrayList<>()));
userService.saveUser(new AppUser(null,"Emma Stone","Emma","1234",true,new ArrayList<>()));
userService.saveUser(new AppUser(null,"Jack Smith","Jack","a1b2c3",false,new ArrayList<>()));
which works fine, but when I rerun the app again , the same users get duplicated again with different ids (so I have 8 users in total).
I want to know if there is a way to handle this without explicitly having to write if statements and checking manually of the username already exists or not.

#OneToMany Pesist Child when creating Parent Entity

I have a OneToMany Relationship (User to EmailAddress)
Maybe I'm going about this the wrong way, My Database is empty but If I want to POST a User object and add it to the Database, along with the emailAdresses object and have the EmailAddress persisted also.
I want 2 records in the Database:
1 User and 1 EmailAddress (with a fk to User table)
Service Class
Currently what I've implemented to get this to work is this:
#Service
public class UserService {
private UserRepository userRepository;
private ModelMapper modelMapper;
public UserService(UserRepository userRepository, ModelMapper modelMapper) {
this.userRepository = userRepository;
this.modelMapper = modelMapper;
//Used for mapping List
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
.setSourceNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
}
public User createUser(UserCreateDTO userCreateDTO) {
User user = modelMapper.map(userCreateDTO, User.class);
//persist User to EmailAddress object
if(user.getEmailAddresses() != null){
user.getEmailAddresses().forEach(user::persistUser);
}
return userRepository.save(user);
}
public UserDTO getUserById(Long id) {
User found = userRepository.findById(id).get();
return modelMapper.map(found, UserDTO.class);
}
// .....
Which I have seen used in some bidirectional relationships
User Entity
#Entity
#Table(name = "Users")
#Getter #Setter #ToString #NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", updatable = false, nullable = false)
private Long id;
private String firstName;
private String lastName;
private int age;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<EmailAddress> emailAddresses;
Email Address Entity
#Entity
#Table(name = "Email")
#Getter #Setter #ToString #NoArgsConstructor
public class EmailAddress {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="email_id", updatable = false, nullable = false)
private Long emailId;
private String email;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
Is there a better way to set up the Join relationship?
Sample POST Request
{"firstName":"Joe", "lastName":"Bloggs", "age": 30, "emailAddresses" : [ "joe-private#email.com" , "joe-work#email.com" ] }
I guess you need to associate this email with a user as well, not just set user to email entity.
public void persistUser(EmailAddress emailAddress) {
// set this email to the user
// if email EmailAddresses list is null you might need to init it first
this.getEmailAddresses().add(emailAddress);
emailAddress.setUser(this);
}
Firstly, I believe that a method persistUser should not be a part of a service layer - due to its implementation it mostly like a Domain layer method that should be implemented within a User entity class.
Secondly, since it's a POST method you shouldn't care of emails existence - you are adding a new user with a new set of emails
Closer to a question, I'd suggest you to try this:
public class UserService {
/************/
#Autowired
private UserManager userManager;
public void addUser(UserModel model) {
User user = new User(model);
List<EmailAddress> emails = model.getEmailAddresses().stream().map(EmailAddress::new).collect(Collectors.toList());
user.setEmailAddresses(emails);
userManager.saveUser(user);
}
}
and at the User add this:
public void setEmailAddresses(List<EmailAddress> emails) {
emails.forEach(item -> item.setUser(this));
this.emailAddresses = emails;
}
And don't forget to implement constructors for entities with model paremeters

#ManyToOne Lazy loading not working

I've seen other posts about this problem, but have found no answer to my own troubles. I have
#Entity
#Table(name= ServerSpringConstants.COMPANY)
public class Company implements Serializable {
private static final long serialVersionUID = -9104996853272739161L;
#Id #GeneratedValue(strategy=GenerationType.AUTO)
#Column (name = "companyID")
private long companyID;
#OneToMany (targetEntity = Division.class, cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch = FetchType.EAGER)
#JoinTable (name = "companyDivisionJoinTable",
joinColumns = #JoinColumn(name="companyID"),
inverseJoinColumns = #JoinColumn(name="divisionID")
)
private Set<Division> divisions = new HashSet<>();
public long getCompanyID() {
return companyID;
}
public Set<Division> getDivisions() {
return divisions;
}
public void setDivisions(Set<Division> divisions) {
this.divisions = divisions;
}
}
On the other side:
#Entity
#Table(name= ServerSpringConstants.DIVISION)
public class Division implements Serializable {
private static final long serialVersionUID = -3685914604737207530L;
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name = "divisionID")
private long divisionID;
#ManyToOne
(fetch = FetchType.LAZY, optional = false, targetEntity = Company.class,
cascade = {CascadeType.PERSIST, CascadeType.MERGE
}
)
#JoinColumn(name="companyID", referencedColumnName = "companyID")
private Company company;
public long getDivisionID() {
return divisionID;
}
public void setDivisionID(long divisionID) {
this.divisionID = divisionID;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
Yet for some reason, LAZY loading not working. I'm using JPA. I'm calling back the companies, and their enclosing divisions from within a 'User' class -- the pertinent part
#ManyToMany (targetEntity = Company.class,
cascade={
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH},
fetch=FetchType.EAGER )
#JoinTable (
name="companyUserJoinTable",
joinColumns=#JoinColumn(name="userID"),
inverseJoinColumns=#JoinColumn(name="companyID")
)
private Set<Company> company = new HashSet<>();
I've searched out existing threads, and have tried adding various suggestions, but nothing has helped!
Any help appreciated.
Thanks!
Since you are loading the divisions set eagerly (with fetch = FetchType.EAGER) and you have a bidirectional association, divisions will be initialized with the parent reference to company. I can't see any problem with it. Jpa loaded the full object tree because you just told it so. A company contains divisions which contain a back reference to the company that loaded them.
To understand it better, since the reason for lazy loading is to reduce the data loaded from database, the owning company is already loaded in session for the divisions, so why not setting the association too?
The #ManyToOne association on the other side takes effect if you load divisions first.
To be more correct with your mapping add also a #MappedBy attribute to the one part of the association. This does not affect fetching behavior but will prevent double updates to the database issued by both ends of the association.

JPA returns multiple objects of the same instance when listing all entities of a class

I have a JPA entity with a list of child entities. In this case a user entity with roles attached to it.
It looks (a bit simplified - some fields/methods omitted) like this:
#Entity
public class MyUser{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long myUserId;
private String username;
#OneToMany
#JoinTable(name = "userrole",
joinColumns = {
#JoinColumn(name="myUserId", unique = true)
},
inverseJoinColumns = {
#JoinColumn(name="roleId")
}
)
private Collection<Role> roles;
public Collection<Role> getRoles() {
return roles;
}
}
If intressting, the Role entity is very simple.
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long roleId;
private String role; // a few more string fields here .
When I add two users and a few hundred roles per user I get a wierd behaviour when I list the users. Each user get's listed a few hundred times (same user = same unique id).
The problematic code:
Query q = em.createQuery("SELECT u FROM MyUser u LEFT JOIN FETCH u.roles");
Collection<MyUser> users = q.getResultList();
for(MyUser u : users){
// print/use u here
}
However, when I just access the database and do select statements, it seems fine. Every user exists only once.
I use OpenJPA 1.2 together with a IBM DB2 database in this case.
I think you have your model wrong, typically a user-role relationship is not OneToMany but "ManyToMany" so you should change your code to look something like this:
#Entity
public class MyUser{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long myUserId;
private String username;
#ManyToMany //This should be many to many
#JoinTable(name = "userrole",
joinColumns = {
#JoinColumn(name="myUserId") //The userId in the join table should
//NOT be unique because the userId can
//be many times with different roles
},
inverseJoinColumns = {
#JoinColumn(name="roleId")
}
)
private Collection<Role> roles;
public Collection<Role> getRoles() {
return roles;
}
}
Try this way and see if it works.
Also your query shouldn't need the Left Join, the roles should be fetched automatically by JPA once you use the getRoles() method on each entity (using LAZY Fetch)
Actually, it's reasonable to have #ManyToMany mapping for User and UserRole entities. The problem with your query is that it returns all the rows from the join table what I believe you don't need. So just add group by u to your query as follows:
SELECT u FROM MyUser u LEFT JOIN FETCH u.roles GROUP BY u
and you'll be done.

Jpa OneToMany not persist child

I've trouble on persistence of OneToMany field. These are two simplified classes
I'm using to make test.
public class User implements Serializable {
...
private String name;
...
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<PhoneNumber> phoneNumbers;
...
}
public class PhoneNumber implements Serializable {
...
private String phoneNumber;
...
#ManyToOne()
private User user;
...
}
So I want to do this:
User u = new User();
PhoneNumber p = new PhoneNumber();
u.setName("Alan");
u.getPhoneNumbers.add(p);
But when I persist the user u the phoneNumber child is not automatically persisted.
In OO way, I only need to do a one to many composition.
I use EclipseLink.
Many thanks to all for your hints.
You need to establish the relationship in both directions. Add p.setUser(u) to your code:
User u = new User();
PhoneNumber p = new PhoneNumber();
u.setName("Alan");
u.getPhoneNumbers.add(p);
p.setUser(u);