Can an entity have more than one many-to-many relations? - java

I have a table that has two different many-to-many relations to two different tables. Let's say I have User <---> UserRole <--> Role and User <--> UserGroups <--> Groups. Since I am new to hibernate and database mapping I was wondering if having my User entity have attributes roles and groups in it, both with #ManytoMany annotations is good practice and acceptable?
i.e.:
#Entity(name = "User")
#Table(name = "USER")
public class User {
.... /* Obviously Id would go here and all other attributes */
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "UserRole", joinColumns = { #JoinColumn(name="USER_ID") },
inverseJoinColumns = { #JoinColumn(name = "ROLE_ID") } )
private Set<Role> roles = new HashSet<Role>();
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "UserGroup", joinColumns = { #JoinColumn(name="USER_ID") },
inverseJoinColumns = { #JoinColumn(name = "GROUP_ID") } )
private Set<Group> groups = new HashSet<Group>();

Sure; lots of types have multiple many-to-many.
I think minimizing many-to-many relationships is a good idea, but it's a natural structure, and is found all over the place.

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 delete entities in many-to-many relationship in Hibernate

I have two entity-classes: Account and Role. These are mapped with the many-to-many relationship. I want to delete one account from database. The code below is working for me, however, I believe there is a better approach.
My Account.class:
public class Account {
//some code
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinTable(name = "account_role",
joinColumns = {#JoinColumn(name = "account_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Role> roles = new HashSet<>();
}
My Role.class:
public class Role {
//some code
#EqualsAndHashCode.Exclude
#ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER,
cascade = {CascadeType.MERGE , CascadeType.PERSIST/*, CascadeType.DETACH, CascadeType.REFRESH*/})
private Set<Account> accounts = new HashSet<>();
}
This is my mapping in PostgreSQL
TL;DR
Code below works fine, what is the better approach?
#Override
public void deleteUserAndHisTokensById(Long accountId) {
Account accountToBeDelete = accountRepository.findDistinctById(accountId);
accountToBeDelete.getRoles()
.forEach(role -> {
Set<Account> updatedAccounts = role.getAccounts()
.stream()
.filter(account -> !account.equals(accountToBeDelete))
.collect(Collectors.toSet());
role.setAccounts(updatedAccounts);
roleRepository.save(role);
});
accountToBeDelete.setRoles(null);
accountRepository.deleteById(accountId);
}
If you add CascadeType.REMOVE, just like #JonathanJohx mentioned. You will be able to remove it just like this:
accountRepository.deleteById();
Moreover, if you want to remove roles from accounts add CascadeType.REMOVE to the other side of the relationship, so you can do this:
Role role = roleRepository.findById(10);
Account account = accountRepository.findById(11);
account.getRoles().remove(role);

How can I make Bridge table from 2 Model classes using hibernate annotation configuration

How to make Bridge table using annotation using Hibernate/JPA configuration.
1: BookModel
2: UserModel
now I have to create a bridge table by these two by fields
book_id and user_id
You are trying to implement Many to Many relationship between your entities. So for this, If you have list of Books in User model, then you can annotate the list as following:
public class UserModel {
#ManyToMany(cascade = CascadeType.REMOVE)
#JoinTable(name = "book_user_table", joinColumns = { #JoinColumn(name = "book_id") }, inverseJoinColumns = { #JoinColumn(name = "user_id") })
private List<BookModel> books;
//Getters and setters
}
and in BookModel, if you have list of users, then you need to use #mappedBy() annotation like following:
#ManyToMany(mappedBy = "books")
private List<UserModel> users;
This will generate the 3rd table, which will have the name book_user_table, with your required columns. See this for detailed explanation: https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
From what I understand from your quesrtion is, you want to know how to map book and user so that there is many to many association between these entities.
If so, you need to specify #ManyToMany on both the associations and make one of them as inverse and the other end with #JoinTable. An example mapping here. Snippet below.
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "Employee_Project",
joinColumns = { #JoinColumn(name = "employee_id") },
inverseJoinColumns = { #JoinColumn(name = "project_id") }
)
Set<Project> projects = new HashSet<>();
On the inverse end,
#ManyToMany(mappedBy = "projects")
private Set<Employee> employees;

Eclipselink merge does not insert a new row in many-to-many relationship

I have a many-to-many relationship between user and role entities. when I try to merge a role by adding new user objects to that, after merging i expect a new row in the joined table (in sql server database) to be inserted but nothing happens.
I guess the problem is the direction of the relationship which the owner is User entity now and should be switched to Role. But if I change it then my spring security wont work. Is there any other way to solve this? except changing many-to-many sides?
Thanks in advance.
User class
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "SEC_USER_ROLE",
joinColumns = {
#JoinColumn(name = "SEC_USER_ID", referencedColumnName = "SEC_USERS")},
inverseJoinColumns = {
#JoinColumn(name = "SEC_ROLE_ID", referencedColumnName = "SEC_ROLES")})
private List<Role> roles;
--
Role class
#ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<User> users;
And here is my merge function
#Override
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public T merge(T o) {
o = em.merge(o);
em.flush();
return o;
}

Optional Many to Many relationship in Hibernate

I want to create M:N relationship as below
Each user can have zero or many ebooks
Each ebook must belongs to one or many users
My mappings in Hibernate :
User.java
#Entity
#Table(name = "USERS")
public class User {
//...
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "USER_EBOOK", joinColumns = #JoinColumn(name = "USER_ID", nullable = false),
inverseJoinColumns = #JoinColumn(name = "EBOOK_ID", nullable = false))
private List<Ebook> listOfEbooks = new ArrayList<Ebook>();
//...
}
Ebook.java
#Entity
#Table(name="EBOOK")
public class Ebook {
//...
#ManyToMany(mappedBy = "listOfEbooks", fetch = FetchType.EAGER)
#NotFound(action = NotFoundAction.EXCEPTION)
private List<User> listOfEbookUsers = new ArrayList<User>();
//...
}
How can I add this additional constraints for example one or many - zero or many?, when I save only ebook object to database there is ebook that does not belongs to anyone.
See this question and the answer of the thread:
Mapping a bidirectional list with Hibernate
And see also this tutorial:
http://viralpatel.net/blogs/hibernate-many-to-many-annotation-mapping-tutorial/
The tutorial provides very good examples of how to implement a proper Many-to-Many mapping.

Categories