JPA ManyToMany Inserting duplicate values - spring

I have the following model:
#Entity
#Table(name="`tbUser`")
public class User {
#Id
#SequenceGenerator(name="tbuser_id_seq",
sequenceName="tbuser_id_seq",
allocationSize=1)
#Column(name="`userId`")
#GeneratedValue
private Long id;
#Column(name="username")
private String username;
#Column(name="password")
private String password;
#Column(name="nombres")
private String firstName;
#Column(name="apellidos")
private String lastName;
#Column(name="email")
private String email;
#Column(name="`avatarUrl`")
private String avatarUrl;
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.MERGE)
#JoinTable(name="`tbUserRole`",
joinColumns=#JoinColumn(name="`userId`",referencedColumnName="`userId`"),
inverseJoinColumns=#JoinColumn(name="`roleId`",referencedColumnName="`roleId`"))
private List<Role> roles = new ArrayList<>();
...
#Entity
#Table(name="`tbRole`")
public class Role {
#Id
#Column(name="`roleId`")
#GeneratedValue
private Long id;
#Column(name="name")
String name;
#ManyToMany(mappedBy="roles")
private List<User> users = new ArrayList<>();
...
Which is mapped to the following tables:
I tried to insert a user with an existing role in the following two ways:
user.getRoles().add(role) and repository.save(new User())
either way, the role is inserted again in the database.
e.g
If these are the existing Roles in the DB:
ID Name
1 ADMIN
2 USER
and I insert a user with an ADMIN role, it is inserted again:
ID Name
1 ADMIN
2 USER
3 ADMIN
How can I resolve this?
I've already tried the solution in this post, but did not work.

The name of the role is not the identifier of the entity nor is it unique. So, the id of the role object will be null if you do something like :
Role role = new Role();
role.setName("ADMIN");
user.getRoles().add(role);
repository.save(user);
JPA will then assume that you passed a new object and will generate a new id for it based on the #GeneratedValue annotation.
Assuming that you use Spring Data you will need to do something like:
Role role = roleRepository.findByName("ADMIN");
user.getRoles().add(role);

Related

Confused why getting a User from Repository fixed "failed to lazily initialize a collection of role" compared to using SecurityContextHolder

My goal was to pass a List of Businesses to the model from the controller to display it in a view and I have succeeded, but have a bit of confusion.
When I initially tried using:
public User getCurrentAuthenticatedUser() {
UserDetailsImpl user = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return user.getUser();
}
#GetMapping("")
public String list(Model model) {
model.addAttribute("businesses", userService.getCurrentAuthenticatedUser().getBusinesses());
return "business/list";
}
I got this error: "failed to lazily initialize a collection of role: com.xyz.User.businesses could not initialize proxy - no Session"
Then I tried:
#GetMapping("")
public String list(Model model) {
int userId = userService.getCurrentAuthenticatedUser().getId();
User user = userService.getById(userId); // gets User using Spring Data JPA UserRepository
List<Business> businesses = user.getBusinesses();
model.addAttribute("businesses", businesses);
return "business/list";
}
And this worked perfectly fine.
What was the issue using the first method. It seemed more simple rather than calling a User from the UserRepository. I've seen some posts that say you should use EAGER fetching, but that's just seems like a bandaid solution.
From the beginner's understanding: Since fetch type is LAZY the businesses don't exist yet in the User but are fetched on demand later on so there shouldn't be an issue.
Edit: After more thought I remembered that with basic Hibernate you would have to create Transactions and commit transactions. I'm assuming that User is not within a Transaction that's why I can't get businesses using the 1st method.
What would be a better solution to fetch the current Authenticated user? And that user's attributes such as a list of businesses.
Model Classes:
Business:
#Entity
#Table(name = "businesses")
public class Business {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private LocalDate date;
#ManyToOne(cascade={CascadeType.MERGE})
#JoinColumn(name="user_id")
private User user;
public Business() {
}
public Business(String name, String description, LocalDate date, User user) {
...
}
public Business(Long id, String name, String description, LocalDate date, User user) {
...
}
... getters/setters
}
USER:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable( name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy="user", cascade={CascadeType.MERGE})
private List<Business> businesses;
... getters/setters
}

How to retrieve only a specific field from child entity on #OneToOne relationship, not all fields?

When I use jpa's #OneToOne annotation, I want to get the userName field from the table, not all fields. What should I do instead?
#Setter
#Getter
#Entity
public class Menu implements Serializable {
private static final long serialVersionUID = 4462798713783196961L;
/**
* id
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToOne
#JoinColumn(name = "createUserId",referencedColumnName = "userId")
private User createUser;
#Column(nullable = false)
private LocalDateTime createTime;
}
What do I need to do, can I get the userName field in the User object, but not all of it? Thank you in advance.
You can create a POJO with required fields. e.g. You only want id from Menu and userName from User:
public class CustomMenu {
private Long menuId;
private String userName;
public CustomMenu(Long menuId, String userName) {
this.menuId = menuId;
this.userName = userName;
}
// getters, setters
}
Then you can write a query with hql using the constructor in the CustomMenu with parameters new com.yourpackage.CustomMenu(m.id, m.createUser.userName) and join User entity (join m.createUser) :
TypedQuery<CustomMenu> query = entityManager.createQuery("select new com.yourpackage.CustomMenu(m.id, m.createUser.userName)"
+ "from com.yourpackage.Menu m join m.createUser", CustomMenu.class);
List<CustomMenu> menus = query.getResultList();
This generates one sql query with inner join fetching only required fields :
select menu0_.id as col_0_0_, user1_.user_name as col_1_0_ from menu menu0_ inner join user user1_ on menu0_.create_user_id=user1_.user_id

Reference Column throwing error in Hibernate

User entity has OneToMany mapping with UserRole entity i.e 1 user can have many Role ids .
I am getting the below error : -
Caused by: org.hibernate.cfg.RecoverableException: Unable to find
column with logical name: USER_ID in
org.hibernate.mapping.Table(t_users) and its related supertables and
secondary tables
#Entity
#Table(name="T_USERS")
public class User {
#Column(name="ID_COL")
#Id
#SequenceGenerator(name="s")
private Integer userId;
#Column(name="USER_NAME ")
private String userName;
#Column(name="USER_EMAIL")
private String userEmail;
#Column(name="PASSWORD")
private String password;
#Column(name="IS_ACTIVE")
private Integer isActive;
#OneToMany
#JoinColumns(
{
#JoinColumn(updatable=false,insertable=false, name="ID_COL", referencedColumnName="USER_ID"),
}
)
private List<UserRole> userRole ;
// removed getters n setters for brevity
}
#Entity
#Table(name="T_MAP_USER_ROLES")
public class UserRole {
#Column(name="MAP_ID")
#Id
#SequenceGenerator(name="seq_user_role")
private Integer mapId;
#Column(name="USER_ID")
private Integer userId;
#Column(name="ROLE_ID ")
private Integer roleId;
#Column(name="IS_ACTIVE")
private Integer isActive;
#OneToOne
#JoinColumns(
{
#JoinColumn(updatable=false,insertable=false, name="ROLE_ID", referencedColumnName="ROLE_ID"),
}
)
private Role role;
What is the cause of the issue and how can I resolve it?
the mapping between UserRole and Role entity is working fine
#JoinColumns is only used for composite foreign keys. In this case, you just need #JoinColumn:
#OneToMany #JoinColumn(updatable=false,insertable=false, name="ID_COL")
Also, please note that you don't need "referencedColumnName" for single column foreign keys.

2 Foreign Keys Into a New Table from Different Entities Hibernate

In my projecet people has role based access.One person can work at more than one departments.
My Role Table
Role_id Role
1 Manager
2 Employee
My Department Table
Departmant_id Departmant
1 Production
2 Research
3 Marketing
My User Table
User_id User_name
1 Jennifer
2 Kate
3 David
What i want is a new table that specifies which people are in which departmant and what role do they have in that department.
User_id Departmant_id Role_id
x x x
What i tried is
Class User{
#ManyToOne(cascade = CascadeType.ALL)
#JoinTable(name = "user_department_role",joinColumns = {#JoinColumn(name = "department_id",referencedColumnName = "department_id"),#JoinColumn(name = "user_id",referencedColumnName = "user_id")}, inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Department> departmentList;
}
You need an association table, often constructed in JPA for various reasons mostly to do with control over what goes in the table or in this case mapping an n-way M:N relationship.
Create all your Entities:
#Entity
public class User {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String userName;
#OneToMany(mappedBy="user")
private Set<UserDepartmentRoleAssociation> associations;
... etc
}
and
#Entity
public class Department {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String department;
#OneToMany(mappedBy="department")
private Set<UserDepartmentRoleAssociation> associations;
... etc
}
and
#Entity
public class Role {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String role;
... etc
}
and create your association table and id class.
#Entity
public class UserDepartmentRoleAssociation {
#EmbeddedId private UserDepartmentRoleAssociationId id;
#ManyToOne #MapsId("userId")
private User user;
#ManyToOne #MapsId("departmentId")
private Department department;
#ManyToOne #MapsId("roleId")
private Role role;
public UserDepartmentRoleAssociation() {
id = new UserDepartmentRoleAssociationId();
}
... etc
}
and
#Embeddable
public class UserDepartmentRoleAssociationId implements Serializable {
private Integer userId;
private Integer departmentId;
private Integer roleId;
... etc
}
and to persist a relationship then ...
User user = new User();
user.setUserName("user1");
Department department = new Department();
department.setDepartment("department 1");
Role role = new Role();
role.setRole("Manager");
UserDepartmentRoleAssociation association = new UserDepartmentRoleAssociation();
association.setUser(user);
association.setDepartment(department);
association.setRole(role);
em.persist(user);
em.persist(department);
em.persist(role);
em.persist(association);
and to read it with join fetch then
User user = em.createQuery("select u from User u left join fetch u.associations ass left join fetch ass.department left join fetch ass.role where u.id = :id", User.class).setParameter("id", 1).getSingleResult();
Note that I have used a Set instead of a List in Department and User which causes much less problems in these cases. Also, I don't have to create associations when I persist the relationship because the UserDepartmentRoleAssociation is the owning entity and therefore does the persisting. The associations sets are created by JPA when it reads a record.

JPQL Special Query

I have two entity bean :
#Entity
public class User {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<Comment> comments = new ArrayList<Comment>();
//SOME OTHER CLASS VARIABLES
//GETTERS AND SETTERS
}
and my Comment class is like this :
#Entity
public class Comment {
#Id
#GeneratedValue
private Long id;
private String title;
private String content;
#ManyToOne
private User user
//SOME OTHER CLASS VARIABLES
//GETTERS AND SETTERS
}
now I know that I can get the User Object from session and set the user for my comment like this in order to be able to use the join feature in JPA:
commentObject.setUser(TheSessionGrabedUserObject/UserObjectWhichHasFetchedFromDbUsingUserId);
but as long as I have the userId for my user Object I do not need to do this.
I'm looking for a way to insert this foreignKey into my comment table without getting the User Object from session or maybe query to database to fetch it first !
how I'm gonna do it using JPQL ?
You can use the entityManager.getReference() method. In your case:
entityManager.getReference(User.class, userId);
This will not perform any DB query, but will give you a User instance with only the ID populated, and you can pass that to commentObject.setUser().

Resources