There are 4 tables in my database. User (which has the role_id column as a foreign key), Role, Permission and an intermediary table named RolePermission (as the relationship between Role and Permission is M:N)
I want to get all the permissions of a specific user based on their role_id.
The intermediary table is defined like this:
CREATE TABLE role_permission
(
role_id TINYINT NOT NULL,
perm_id TINYINT NOT NULL,
CONSTRAINT role_permission_pk PRIMARY KEY (role_id, perm_id),
CONSTRAINT role_permission_f1 FOREIGN KEY (role_id) REFERENCES role (rid),
CONSTRAINT role_permission_f2 FOREIGN KEY (perm_id) REFERENCES permission (pid)
);
and the entity related to this table is like this:
#Entity
#Table(name = "role_permission")
public class RolePermission {
#EmbeddedId
private RolePermissionPK id;
#ManyToOne
#JoinColumn(name = "role_id", referencedColumnName = "rid", insertable = false, updatable = false)
private Role role;
#ManyToOne
#JoinColumn(name = "perm_id", referencedColumnName = "pid", insertable = false, updatable = false)
private Permission permission;
...
}
but when I'm trying to get this field in my User class, it returns nothing:
#OneToMany(mappedBy = "role")
private List<RolePermission> permissions;
The database works properly and all the entities including Role, Permission and RolePermission are working flawlessly too. But I can't figure out the missing link between the RolePermission class and the User class.
The M:N connection between the Role and Permission tables should be defined in the Role table like this:
#ManyToMany
#JoinTable(
name = "role_permission",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "perm_id")
)
List<Permission> permissions;
and then the foreign key in the User entity should be connected to the Role entity:
#ManyToOne
#JoinColumn(name = "role_id", referencedColumnName = "rid")
private Role role;
Related
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
I have the roles field in User entity:
#Entity
#Table(indexes = {
#Index(columnList = "uuid")
})
public class User
...
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#NotEmpty
private Set<Roles> roles;
when I try to delete user with a query, the referential integrity constraint violation occurs, because user is referenced from User_roles table.
How to solve this in any way?
DDL for related table shows
create or replace table User_roles
(
User_id bigint not null,
roles varchar(255) null,
constraint FKi81fp6mx433heb7dvbxqaqvpv
foreign key (User_id) references User (id)
);
i.e. it doesn't contain ON DELETE CASCADE clause. I need it be there.
You can add ON DELETE CASCADE clause to the FOREIGN KEY constraint in the following way:
import javax.persistence.ForeignKey;
#Entity
#Table(name ="User")
public class User
{
#ElementCollection
#CollectionTable(
name = "User_roles",
joinColumns = #JoinColumn(name = "User_id"),
foreignKey = #ForeignKey(
name = "user_fk",
foreignKeyDefinition = "FOREIGN KEY (User_id) REFERENCES User(id) ON DELETE CASCADE")
)
#Enumerated(EnumType.STRING)
private Set<Roles> roles;
}
I have 3 entity classes like below:-
Role Entity
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(name = "roles_privileges", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges;
// getters, setters etc
}
Privilege Entity
#Entity
public class Privilege {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "privileges", fetch = FetchType.EAGER)
#JsonIgnore
private Set<Role> roles;
// getters, setters etc
}
UrlsMapper Entity
#Entity(name = "urls_mapper")
#Table(name = "urls_mapper")
public class UrlsMapper {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
#Column(name = "http_method")
private HttpMethod httpMethod;
#Column(name = "path")
private String path;
#ManyToMany(fetch = FetchType.EAGER)
#MapKeyJoinColumn(name = "role_id", referencedColumnName = "id")
#JoinTable(
name = "u_r_p",
inverseJoinColumns = #JoinColumn(name = "privilege_id")
)
Map<Role, Privilege> privilegeMap;
// getters, setters etc
}
The keys, primary and foreign that get created are as below
The logs while table generation is as below:-
Hibernate: create table u_r_p (urls_mapper_id bigint not null, privilege_id bigint not null, role_id bigint not null, primary key (urls_mapper_id, role_id)) engine=InnoDB
Hibernate: alter table u_r_p add constraint FKgd7gd9f9ded1s28swdudqs0ro foreign key (privilege_id) references Privilege (id)
Hibernate: alter table u_r_p add constraint FKrryprkx4j60lyjti16eysn5g5 foreign key (role_id) references Role (id)
Hibernate: alter table u_r_p add constraint FKfkthdnoca59a18ba96183p7ov foreign key (urls_mapper_id) references urls_mapper (id)
And I just want to know how can I add the privilege_id also into the JoinTable u_r_p and if there can be other best options for this. Manually doing in the database is a obvious alternate, but i wanted to know the hbm2ddl.auto based solution, so that code manages it itself
I don't think you've modeled your concepts properly. You have a ManyToMany between Role and Priviledge but what makes UrlMapper an entity? You have a Map<Role, Privilege> field in UrlMapper but that is the purpose of the join table so there should be no need to duplicate that. Instead it seems to be that HttpMethod and Path are attributes of the relationship.
However, I might also note that you seem to be expecting there be a Role/Privilege join for many different HttpMethod/Path combinations. This seems incredibly fine grained and an operations nightmare, but whatever. Anyway, what you seem to be saying is you want unique combinations of Role/Privilege/HttpMethod/Path so you should just make a entity for that and the table represents your set. Make a Permission entity that holds a unique Role/Privilege/HttpMethod/Path. Role, Privilege, HttpMethod, and even Path are essentially enumerations so you should have a table for each for each of them with ManyToOne mappings in the Permission entity. You could add bidirectional OneToMany mappings in each of the lookup tables but I'm not sure I see a need for that. It's up to you.
I assume Privilege would be {allow, deny} but it seems like less of a tangle if you assume deny unless a Role/HttpMethod/Path permission specifically exists. If that's the case then I would leave out the Privilege entity. Anyway, just a thought. Hope this helps.
#ManyToOne
#JoinColumn(name = "someValue" , referencedColumnName = "someOtherValue" )
What values are to be placed in name and referencedColumnName column if 2 tables are linked by ManyToOne association?
Suppose you have Two tables:
1. Department table with columns:
a. Dept_ID (primary key)
b. Dept_Name
2. Employee Table with following column:
a. Emp_Id (primary key)
b. Dept_Id_fk (foreign key)
c. Salary
Now your join column for Employee Entity class will be
#ManyToOne
#JoinColumn(name = "Dept_Id_fk", referencedColumnName = "Dept_ID")
Department department;
So referencedColumnName means column that you are referencing to from your foreign key column.
How would the join column look like for oneToMany association? Here is an example:
Person table:
person_id (pk), person_name
person_reltn table:
person_reltn_id (pk),
child_person_id (fk),
parent_person_id (fk)
For the above tables, if I were to create the Person entity:
class Person(){
#Id
#Column(name = "PERSON_ID")
private long personId;
#NotFound(action = NotFoundAction.IGNORE)
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(
name = "CHILD_PERSON_ID",
referencedColumnName = "PERSON_ID",
updatable = false,
insertable = false)
#Where(clause = "ACTIVE_IND = 1")
#Filter(
name = FilterConstants.END_EFFECTIVE_DATE_TIME_FILTER,
condition = FilterConstants.END_EFFECTIVE_DATE_TIME_CONDITION)
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
private final List<PersonRelation> personRelations = new ArrayList<>();
}
In the joinColumn, should the name always have the foreign key (which means the value from entity you are joining to) and the referenceColumnName should alway have the primary key on the entity? If yes, it will be the opposite of Sayantan's response above. Please let me know if I misunderstood the concept.
Update on 03/04/2021
After doing more research, I found the documentation on how to set the referenceColumnName based on the entity mappings[1]. Looks like, for unidirectional OneToMany mapping, the referenced column is in the table of the source entity.
1.https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/JoinColumn.html
I have a ManyToMany relation between two entity
#javax.persistence.Entity
#Table(name = "t_aircraft_model")
public class AircraftModel extends DbObject {
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "t_aircraft_model_entity", joinColumns = { #JoinColumn(name = "aircraft_model_uid", nullable = false) }, inverseJoinColumns = { #JoinColumn(name = "entity_id_LDAP", nullable = false) })
private List<com.airbushelicopter.ahead.db.pojo.Entity> entities ;
But sqlServer doesn't allow me to publish the intermediate table : t_aircraft_model_entity
I thought about 2 solutions
Both column of the table the t_aircraft_model_entity become the primary key (ok in my case a aircraft can't be linked multiple time to the same entity)
I add a 3rd column (id) which will be the primary key
Or ?
But I have no idea how I can do this with hibernate and annotation.
thanks !
First things first. You will need 3 tables to make a many to many relation, of course, you will need to make sure that both of your other tables have a PK
On the code side, you can do like this:
Your Airplace Model:
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "t_aircraft_entity_relation",joinColumns = {
#JoinColumn(name = "aircraftid", nullable = false, updatable = false)},
inverseJoinColumns = { #JoinColumn(name = "entityid",nullable = false,updatable= false)
})
private Set<com.airbushelicopter.ahead.db.pojo.Entity> entities ;
On your Entity Model:
#ManyToMany(fetch = FetchType.EAGER,mappedBy="entities")
private Set<AircraftModel> aircrafts;
And you will have to create a relation table, like in my example:
CREATE TABLE t_aircraft_entity_relation
(
aircraftid integer NOT NULL,
entityid integer NOT NULL,
CONSTRAINT "PK_AIRCRAFT_ENTITY" PRIMARY KEY (aircraftid, entityid),
CONSTRAINT "FK_AIRCRAFT_ENTITY" FOREIGN KEY (aircraftid)
REFERENCES t_aircraft_model (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "FK_ENTITY_AIRCRAFT" FOREIGN KEY (entityid)
REFERENCES t_entity_model (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
PS: This piece of SQL is based on Postgresql, so you will have to do a little bit of change.