Delete ManyToMany link - java

I'm newbie in Hibernate and I've a question about it.
I've 3 tables: User, Role and UserRole.
User and Role have ManyToMany relationship mapped by UserRole.
I have 2 rows in Role: (1, ROLE_ADMIN) (2, ROLE_USER).
I have 1 row in User: (1, TESTUSER).
I have 2 rows in UserRole: (1,1)(1,2).
I have 2 Entities: UserEntity and RoleEntity.
I added 2 role to TESTUSER calling method .add( Role ) on UserEntity and after .save( User ) on Session.
I can add all role i want to TESTUSER and it's works!
When i call .remove( Role ) on UserEntity and after .save( User ) on my JPA repository. It doesn't work.
I mean that hibernate doesn't execute any query to delete elements by table UserRole. Why?
Can you help me?
RoleEntity
#Entity
#Table(name = "role" , uniqueConstraints= {
#UniqueConstraint(columnNames={ "role_id" }) ,
#UniqueConstraint(columnNames={ "name" })
})
#Indexed
public
class Role
implements Serializable
{
#ManyToMany( mappedBy="roleList", fetch = FetchType.LAZY )
#Fetch(FetchMode.SELECT)
#BatchSize(size = 100)
private List<User> userList = new ArrayList<>(0);
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id", nullable = false)
private
Long roleId;
#Column(name = "name", nullable = false)
private
String name;
UserEntity
#Entity
#Table(name = "user" , uniqueConstraints= {
#UniqueConstraint(columnNames={ "user_id" }) ,
#UniqueConstraint(columnNames={ "name" })
})
#Indexed
public
class User
implements Serializable
{
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_role")
#Fetch(FetchMode.SELECT)
#BatchSize(size = 100)
private List<Role> roleList = new ArrayList<>(0);
#Column(name = "name", nullable = false)
private
String name;
My test code:
Role adminRole = new RuoloBuilder().setName("ROLE_ADMIN").build();
Role userRole = new RuoloBuilder().setName("ROLE_USER").build();
adminRole = roleService.saveOrUpdate(adminRole);
userRole = roleService.saveOrUpdate(userRole);
User user = new UtenteBuilder()
.setName("TESTUSER")
.setRoleList(Arrays.asList(adminRole, userRole))
.build();
user = userService.saveOrUpdate(user); // It works
user.getRoleList().remove(userRole);
userService.saveOrUpdate(user); // It doesn't work

#ManyToMany(fetch = FetchType.LAZY,cascade=CascadeType.ALL) // on the entity User
You have to cascade all the operations PERSIST, REMOVE, REFRESH, MERGE, DETACH to the linked entities (Roles).

Related

JPA #JoinTable with composite (2 column) primary keys

In a spring-boot app, I've got the following entity definition:
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#JoinTable(name = "userrole",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
I'm using Spring-data-jpa,Hibernate with H2 as the database.
The trouble is that spring-data-jpa, hibernate always generate/creates the join table (DDL) 'userrole' with a single column primary key. e.g. 'username'.
Hence, if records such as {'username', 'user_role'} and {'username', 'admin_role'} is inserted in the join table ('userrole'), the next insert fails with an error due to the 'duplicate' primary key.
I've tried using both columns in the above definition, as well as the following variation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name = "username"),
#JoinColumn(name = "role") })
private List<Role> roles;`
But that they resulted in the same or worse problems, e.g. and in the latter, even table creation fails because only a single column is used as primary key for the jointable. Role is simply another table with 2 columns 'role' and 'description', basically a role catalog.
How do we specify to JPA that the #JoinTable should use both 'username' and 'role' columns as composite primary keys?
edit:
I tried using an independent table/entity as suggested, thanks #Kamil Bęben
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
#ElementCollection
private List<UserRole> roles;
UserRole is defined as such
#Data
#NoArgsConstructor
#Entity
#Table(name = "userrole")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
#Column(nullable = false, name = "username", length = 100)
private String username;
#Column(nullable = false, name = "role", length = 50)
private String role;
the repository for that user-roles join table is defined as
#Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
Admittedly, ugly, but that it works. And that somehow, it seemed to use the correct findByUsername() method to retrieve the roles as is relevant to the user, probably related to the 'mappedBy' clause. 'black magic'! There's lots more that I'd still need to find my way around JPA, Spring, Spring-data
edit2:
further update:
the original #JoinTable works as well.
But that the relations need to be specified as #ManyToMany
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
#JoinTable(name = "usersroles",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
This creates 2 column primary keys as expected for the 'users-roles' table
Thanks to #Roman
If Role only has two columns, eg user_id and role, the way to map this in jpa would be as following
#ElementCollection
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role")
List<String> roles = new ArrayList<>();
Otherwise, jpa really requires each entity's identifier and join columns to be separate columns, so Role entity would have to have columns like id, user_id and role_name. Could look like this .:
class Role {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
And in the User entity
#OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Further reading

How to join two entities with ManyToMany relationship in the jpa query

I have two entities User and Role. Each user can have multiple roles.
User class
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
Role class:
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
private String name;
}
So a new joined table is being created called: user_role
I want to create a query for returning a list of users with role_id of 4, for example.
The query that I already tried:
#Override
public List<User> getArtists() {
return em.createQuery(
"from User u, Role r where u.roles='4'",
User.class
).getResultList();
}
How can I fix this query in order to retrieve a list of users with role_id of 4?
You can do something like this:
List<User> users = em.createQuery(
"select distinct u from User u join fetch u.roles rl where rl.id = :id",
User.class)
.setHint( QueryHints.HINT_PASS_DISTINCT_THROUGH, false )
.setParameter("id", 1L)
.getResultList();
The QueryHints.HINT_PASS_DISTINCT_THROUGH is added as an additional performance optimization. But please note that this optimization will work only with hibernate 5.2.2.Final ... 5.2.11.Final. It was broken in the 5.2.12.Final.
If I were you, I will get the benfit of using SpringdataJpa with hibernate and just use this statment :
If you don't want to use query :
List<User> findByRoles_Id(Long id);
In your User Repository :
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByRoles_Id(Long id);
}

Foreign Key mapping using model mapper

I am getting a JSON which is deserialised into a POJO and I am trying to map a POJO to below entity class using modelmapper and trying to save these entities into the database.All the fields are getting mapped and saved as expected except the foreign key value which is coming as null. Also, the userid is auto generated.Can you please help me how can I save the user id in vehicle table using modelmapper??
My User Entity class
#Entity
#Table(name = "usr")
class User{
#Id
#Column(name = "user_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
private String username;
#OneToMany(mappedBy = "user",cascade = CascadeType.ALL)
private List<Vehicle> vehicles= new ArrayList<>();
//getters and setters
}
My Vehicles Entity Class:
#Entity
#Table(name = "vehicle")
class Vehicle{
#Id
#Column(name = "vehicle_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long vehicleId;
private String vehicleName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_fk" , nullable = false)
private User user;
//getters and setters
}
My ModelMapping class:
#Service
public class UserService {
#Autowired
private UserDetailsService userDetailsService;
public void saveUserDetails(UserDetails userDetails
User targetuser{
UserDetails sourcePojo= userDetails
User targetEntity= targetuser
ModelMapper modelMapper = new ModelMapper();
modelMapper.map(sourcePojo, targetEntity);
userDetailsService.save(targetEntity);
}
}
You Can try with making annotation more specific this :
#OneToMany(mappedBy = "user",
targetEntity = Vehicle.class,
cascade = CascadeType.ALL)
private List<Vehicle> vehicles= new ArrayList<>();
And
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_fk" ,
referencedColumnName = "user_id",
nullable = false)
private User user;
And I think you have to remove annotation for auto generation of userid
As per my understanding issue is due to Auto generation of userid is done while saving an entry to DB. But when we mapped userid to vehicle it is null as it is not generated.

Insertion in Many to Many unidirectional relationship in Spring JPA

I have two tables, Users, and Roles. Roles are like "ADMIN", "MANAGER","USER". Users can have of the roles. So in my java project, I have the user as
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Integer userId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name =
"user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
//other data and getters and setters
}
My Role class is like
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "role_id")
private Integer roleId;
#Column(name = "role_name",unique=true)
private String role;
//getters and setters
}
Let's say I have inserted manually 3 records to Roles
INSERT INTO `role` VALUES (1,'ADMIN');
INSERT INTO `role` VALUES (2,'MANAGER');
INSERT INTO `role` VALUES (3,'USER');
Now I want to insert to User table such that only user and user_roles(join table) get inserted:
Eg:
If i want to insert an user with userId=1 and role={ADMIN,MANAGER}, an entry in user table and 2 entries in user_roles table like (1,1) and (1,2) should get inserted. There should not be any insertion to the Roles table. How do I achieve this?
I tried with changing manytomany to onetomany... Also, I tried changing cascadeType.all to CascadeType.MERGE and detach... none of them worked correctly...
please help
//UPDATE:
Adding code related to create/update user
public User createUser(UserDto account) {
User newUser = new User();
newUser.setPassword(account.getPassword());
newUser.setUsername(account.getUsername());
Set<Role> roles = new HashSet<Role>();
Role role = roleRepository.findByRole(account.getRole());
if (role != null) {
role.setRole(account.getRole());
roles.add(role);
newUser.setRoles(roles);
User savedUser = save(newUser);
return savedUser;
}
return null;
}
#Transactional
public User save(User user) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
try {
user = userRepository.save(user);
} catch (org.springframework.dao.DataIntegrityViolationException ex) {
log.error(ex.getMessage());
return null;
}
return user;
}
#Override
public User updateUser(String oldUserName, UserDto userDto) {
Optional<User> optionalUser = userRepository.findByUsername(oldUserName);
optionalUser.orElseThrow(() -> new UsernameNotFoundException("User not found"));
// if (optionalUser != null) {
User user = optionalUser.get();
if (user != null) {
Set<Role> roleset = new HashSet<Role>();
if (userDto.getRole() != null && !userDto.getRole().isEmpty()) {
Role role = roleRepository.findByRole(userDto.getRole());
if (role != null) {
roleset.add(role);
}
}
user.setRoles(roleset);
user.setUsername(userDto.getUsername());
user = userRepository.save(user);
return user;
} else
return null;
}
When i tried with
#ManyToMany(cascade = {CascadeType.MERGE,CascadeType.PERSIST}, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
i get the following exception
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.techjava.springbootsecuritymysql.model.Role
The error detached entity passed to persist is caused by the role being detached from the entityManager. The reason for that is, that it is read outside the Transaction but the association in user_roles is inserted within the transaction.
To solve that problem simply add #Transactional to the createUser method. Then the Role is read in the same transaction and won't be detached:
#Transactional
public User createUser(UserDto account) {
//...
}
The other thing is, that you set the cascade on the #ManyToMany. This is not needed if only the associations should be automatically persisted and deleted with the User. They will always be automatically created and deleted no matter what the cascase options are.
Cascade would be needed when you for example want the Role itself to be automatically inserted with the User which I assume you do not want from your code sample. So just remove the cascade:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;

how to write hql query for hibernate OneToMany relationship?

I have the following classes laid out like so:
class User {
#Id
private long id;
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Set<UserRole> userRoles;
}
class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private int id;
#Column(name = "role", nullable = false, length = 45)
private String role;
}
I'm attempting to query for users that have a specific role (in this case, all users with the role ROLE_ADMIN), using the following query:
org.hibernate.query.Query<User> userQuery=session.createQuery("from User as mc where mc.userRoles in (from UserRole as ur where ur.role in (:uRoles))",User.class);
Set<String> roles = new HashSet<String>();
roles.add("ROLE_ADMIN");
userQuery.setParameterList("uRoles", roles);
List<User> admins = userQuery.getResultList();
However, the query is not returning any results.
session.createQuery(
"from User as user join user.userRoles as userRole where userRole.role in :roles")
.setParameter("roles", Arrays.asList("ROLE_ADMIN"))
.getResultList();

Categories