I have a following Entities. Means that User can belong to many businesses and for each business this user can has separate permissions. So an existing user can be assigned to another business. The Business_User table looks like this:
USER_ID BUSINESS_ID AUTHORITY_NAME
6 1 ROLE_ANALYTICS
6 1 ROLE_SELF_ANALYTICS
7 1 ROLE_REVIEWS
8 1 ROLE_ANALYTICS
8 1 ROLE_SELF_ANALYTICS
8 1 ROLE_REVIEWS
6 2 ROLE_REVIEWS
6 2 ROLE_SELF_ANALYTICS
Question: I am querying Users for ONE business by trying to build list of user DTO objects, that DTO exposes List<Authority>, problem is that I can't figure how should I get these authorities for each user from the Business_User table. Been trying to do fancy stuff with lambdas but have no luck. I am using Spring Data for queries, maybe can solve it in the repository.
Thanks in advance.
EDIT: If I would go another route by adding new join table BUSINESS_USER_AUTHORITY, how would I have to describe it in UserBusiness class? I also would like the primary key to be over user_id and business_id. Note that name is PK in Authority table.
Something like this, but that does not create me join table at all.
Change UserBusiness class:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "BUSINESS_USER_AUTHORITY",
joinColumns = {#JoinColumn(name = "business_user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "authority_name", referencedColumnName = "name")})
private Set<Authority> authorities = new HashSet<>();
User
#Entity
#Table(name = "USER")
public class User extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "user_id", referencedColumnName = "id")
})
private Set<BusinessUser> businessUsers = new HashSet<>();
}
BusinessUser
#Entity
#Table(name = "BUSINESS_USER")
#IdClass(BusinessUser.class)
public class BusinessUser implements Serializable {
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "business_id")
private Business business;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "authority_name")
private Authority authority;
}
Business
#Entity
#Table(name = "BUSINESS")
public class Business implements Serializable {
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "business_id", referencedColumnName = "id")
})
private Set<BusinessUser> businessUsers = new HashSet<>();
}
Authority
#Entity
#Table(name = "AUTHORITY")
public class Authority implements Serializable {
#NotNull
#Size(min = 0, max = 50)
#Id
#Column(length = 50)
private String name;
}
I ended up writing a function like that, but if anyone has a solution how would the design look with another join table between BusinessUser <--> BusinessUserAuthority then I'd be glad to implement it.
private Function<Map.Entry<User, List<BusinessUser>>, UserManagementDTO> getPermissionForUser() {
return user -> {
UserManagementDTO dto = userManagementMapper.userToUserManagementDTO(user.getKey());
dto.setAuthorities(user.getValue().stream().map(BusinessUser::getAuthority).collect(Collectors.toSet()));
return dto;
};
}
Related
I am working on a springboot application. I have 2 entity classes, Group and User. I also have #ManyToMany relationship defined in the Group class (Owning entity), and also in the User class, so that I can fetch all the groups a user belongs to. Unfortunately, I can't create a new group or a new user due to the following error;
{
"timestamp": "2022-09-09T20:29:22.606+00:00",
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'application/json;charset=UTF-8' not supported"
}
When I try to fetch all groups a user belongs to by calling user.get().getGroups(); I get a a stack overflow error
Note: Currently I have #JsonManagedReference and #JsonBackReference in Group and User classes respectively. I also tried adding #JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") on both classes, but this did not work either. Adding value parameter to #JsonManagedReference and #JsonBackReference as demonstrated below did not work either. What am I doing wrong? What am I missing?
This is my Group entity class
#Table(name = "`group`") // <- group is a reserved keyword in SQL
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#JsonView(Views.Public.class)
private String name;
private Integer maximumMembers;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.ALL})
#JoinTable(name = "group_user", joinColumns = #JoinColumn(name = "group_id"), inverseJoinColumns = #JoinColumn(name = "user_id"))
#JsonView(Views.Public.class)
#JsonManagedReference(value = "group-member")
private Set<User> groupMembers;
}
This is my User entity class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(Views.Public.class)
private Long id;
#JsonView(Views.Public.class)
private String nickname;
#JsonView(Views.Public.class)
private String username; // <- Unique user's phone number
private String password;
#ElementCollection(targetClass = ApplicationUserRole.class)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
#Column(name = "role")
private Set<ApplicationUserRole> roles;
#ManyToMany(mappedBy = "groupMembers", fetch = FetchType.LAZY, targetEntity = Group.class)
#JsonBackReference(value = "user-group")
private Set<Group> groups;
}
Minimal, Reproducible Example https://github.com/Java-Techie-jt/JPA-ManyToMany
I found a permanent solution for this problem. For anyone else facing a similar problem, This is what I found. First, my entity classes had #Data Lombok annotation. I removed this because the #Data annotation has a tendency of almost always loading collections even if you have FetchType.LAZY.
You can read more about why you should't annotate your entity class with #Data here https://www.jpa-buddy.com/blog/lombok-and-jpa-what-may-go-wrong/
After removing this annotation, I removed #JsonManagedReference and #JsonBackReference from both sides of the relationship(both entities). I then added #Jsonignore to the referencing side only(User class). This solves 2 things
Creating a group with a list of users works fine
Adding a list of users to a group works fine.
After this, we are left with one last problem. When we try to read a user from the api, we get a user without the associated list of groups they belong to, because we have #JsonIgnore on the user list. To solve this, I made the controller return a new object. So after fetching the user from my service, I map it to a new data transfer object, the I return this object in the controller.
From here I used #JsonView to filter my responses.
This is how my classes look, notice there is no #Data in annotations.
Group
#Builder
#Entity
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
#Table(name = "`group`") // <- group is a reserved keyword in SQL
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Integer maximumMembers;
#ManyToMany(fetch = FetchType.EAGER,
cascade = {CascadeType.MERGE, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinTable(name = "group_user",
joinColumns = #JoinColumn(name = "group_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
#JsonView(UserViews.PublicUserDetails.class)
private Set<User> groupMembers = new HashSet<>();
}
User
#Builder
#Entity
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(UserViews.PublicUserDetails.class)
private Long id;
#JsonView(UserViews.PublicUserDetails.class)
private String nickname;
#JsonView(UserViews.PublicUserDetails.class)
private String username; // <- Unique user's phone number
private String password;
#ElementCollection(targetClass = ApplicationUserRole.class)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
#Column(name = "role")
#JsonView(UserViews.PublicUserDetails.class)
private Set<ApplicationUserRole> roles;
#JsonIgnore
#ManyToMany(mappedBy = "groupMembers", fetch = FetchType.LAZY, targetEntity = Group.class)
private Set<Group> groups = new HashSet<>();
}
Method fetching user in user controller
#GetMapping("/get-groups")
public ResponseEntity<UserRequestResponseDTO> getWithGroups(#RequestParam(name = "userId") Long userId) {
User user = userService.getWithGroups(userId);
UserRequestResponseDTO response = UserRequestResponseDTO.builder()
.nickname(user.getNickname())
.username(user.getUsername())
.groups(user.getGroups())
.build();
return ResponseEntity.ok().body(response);
}
Hopefully this helps someone💁
I currently have a problem with this Relationship, I have tried everything I saw on the internet. Still, I get this error: ERROR: column roles0_.user_id does not exist.
I have a boot app that has spring security, and I need to login using users from PostgreSQL database.
But I just can't get the relation between the user and the Role to work.
Here are Entity classes:
#Data
#Entity
#Table(name="user",schema = "public")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer id;
#Column(unique = true)
private String username;
private String password;
private boolean enabled;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
private List<Role> roles;
}
#Data
#Entity
#Table(name="role",schema = "public")
public class Role {
#Id
#Column(name="role_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne()
#JoinColumn(name ="user_id")
private User user;
}
The database looks fine, I looked at the column names, etc. I don't know what to do to get rid of this error. I have the user table, and another table named roles, which include id and name, 2 inputs, USER and ADMIN...
It seems that the #JoinColumn annotation requires one additional column in the roles table the one with #ManytoOne relation, because when I add the column the error disappears, but when I'm trying to get the role from each user, I get an empty List. The foreign key is set as well, from the roles column to the role_id column from role table.
worked for me this way:
#Entity
#Data
#Table(name = "users")
public class User{
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")})
private List<Role> roles;
}
and then in roles just:
#Entity
#Table(name = "roles")
public class Role{
#ManyToMany(mappedBy = "roles", fetch = LAZY)
private List<User> users;
}
that's if you are ok with third table user_roles (user_id, role_id) which manages the many to many relation
User table :
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.EAGER)
private List<Role> roles;
Role table :
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
I'm trying to map up an existing database schema using Hibernate+JPA annotations.
One of my entities are mapped like this:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
private int department;
#Id
private int userId;
...
And another entity, Group:
#Entity
#Table(name = "groups")
public class Group implements Serializable {
#Id
private int department;
#Id
private int groupId;
...
Group and User should have a many-to-many relationship between them, but the issue is that the join table ("user_group") only has columns "DEPARTMENT, USERID, GROUPID" - i.e. the DEPARTMENT column needs to be used in both joinColumns and inverseJoinColumns:
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "user_groups",
joinColumns = { #JoinColumn(name = "department"), #JoinColumn(name = "groupid") },
inverseJoinColumns = {#JoinColumn(name = "department"), #JoinColumn(name = "userid") }
)
private List<User> groupUsers = new ArrayList<>();
which gives a mapping error - "Repeated column in mapping for entity".
However, it looks like this was/is possible using XML, because this exact example exists in the old Hibernate documentation. But I cannot find any evidence that this ever worked using annotations? I tried with #JoinFormula instead of #JoinColumn, but that does not compile. Is it possible?
Okay, I'm pretty sure it's not possible.
I found a promising workaround:
Create an #Embeddable for the "user_group" table:
#Embeddable
public class UserGroupMembership implements Serializable {
#ManyToOne
#JoinColumnsOrFormulas(
value = {
#JoinColumnOrFormula(column = #JoinColumn(referencedColumnName = "userid", name = "userid")),
#JoinColumnOrFormula(formula = #JoinFormula(referencedColumnName = "department", value = "department"))
})
private User user;
public UserGroupMembership(User user) {
this.user = user;
}
public UserGroupMembership() {
}
public User getUser() {
return user;
}
}
The trick is that #ManyToOne allows you to use #JoinColumnsOrFormulas, so one of the join conditions can be a formula, which I doesn't seem to work for #ManyToMany (the #JoinColumnsOrFormulas annotation is ignored as it expects the join columns to be part of the #JoinTable annotation).
The UserGroupMemberships are then mapped as a ElementCollection:
#ElementCollection
#CollectionTable(name = "user_group", joinColumns = {
#JoinColumn(name = "department", referencedColumnName = "department"),
#JoinColumn(name = "groupid", referencedColumnName = "groupid")
})
#OrderColumn(name = "seq", nullable = false)
private List<UserGroupMemberships> groupUsers = new ArrayList<>();
This only works right now for a unidirectional many-to-many relationship.
I'm looking for a way to implement (0..n) many to many relation in JPA, much possibly using #ManyToMany annotation. All examples that I found were about (1..n) relations. What I need to accomplish:
- I've got two entities: Contact and Tag. Each Contact can have 0..n Tags. Each Tag can have 0..n Contacts. From SQL point of view it would look like
this: Contact (0..n) --- (1) Contact_has_Tag (1) --- (0..n) Tag.
Code below is not working for me because JPA is linking columns with INNER JOIN.
OFC I could do this using intermediate entity and #OneToMany and #ManyToOne annotations, but I want a simpler sollution.
#Data
#Entity
public class Contact {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private long id;
#Column(unique = true)
private String email;
// ...
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonIgnore
#JoinTable(
name = "contact_has_tag",
joinColumns = #JoinColumn(name = "contact_id", referencedColumnName = "id", updatable = false, nullable = true),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "id", updatable = false, nullable = true))
private List<ContactTag> contactTags = new ArrayList<ContactTag>();
}
#Entity
#Data
public class ContactTag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column
private String name;
#ManyToMany(mappedBy="contactTags", fetch = FetchType.LAZY)
#JsonIgnore
private List<Contact> contacts = new ArrayList<Contact>();
}
Any ideas how it should be done?
i have some join tables like this multimedia_feature.
The wizard will create in Multimedia class a List attribute:
...
#JoinTable(name = "multimedia_feature", joinColumns = {
#JoinColumn(name = "feature_oid", referencedColumnName = "oid")}, inverseJoinColumns = {
#JoinColumn(name = "multimedia_oid", referencedColumnName = "oid")})
#ManyToMany
private List<Feature> featureList;
...
since i just need a ManyToOne relationship (one feature can have many multimedia file), i flagged multimedia_oid as UNIQUE.
After this the wizard creates other 2 tables (i think redoundant)
#Entity
#Table(name = "multimedia_feature")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "MultimediaFeature.findAll", query = "SELECT m FROM MultimediaFeature m"),
#NamedQuery(name = "MultimediaFeature.findByMultimediaOid", query = "SELECT m FROM MultimediaFeature m WHERE m.multimediaFeaturePK.multimediaOid = :multimediaOid"),
#NamedQuery(name = "MultimediaFeature.findByFeatureOid", query = "SELECT m FROM MultimediaFeature m WHERE m.multimediaFeaturePK.featureOid = :featureOid")})
public class MultimediaFeature implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected MultimediaFeaturePK multimediaFeaturePK;
#JoinColumn(name = "multimedia_oid", referencedColumnName = "oid", insertable = false, updatable = false)
#OneToOne(optional = false)
private Multimedia multimedia;
#JoinColumn(name = "feature_oid", referencedColumnName = "oid", insertable = false, updatable = false)
#ManyToOne(optional = false)
private Feature feature;
...
...
and
#Embeddable
public class MultimediaFeaturePK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "multimedia_oid")
private int multimediaOid;
#Basic(optional = false)
#NotNull
#Column(name = "feature_oid")
private int featureOid;
...
...
finally it added an attribute in Multimedia class:
....
#OneToOne(cascade = CascadeType.ALL, mappedBy = "multimedia")
private MultimediaFeature multimediaFeature;
....
since I have really many join classes, I would avoid creating all of these classes.
Can i manually create the attributes, such as:
#JoinTable(name = "multimedia_feature",
#JoinColumn(name"feature_oid", referencedColumnName = "oid")
)
#OneToOne(optional = false)
private Feature feature;
or this precludes the correct persistence?
It looks the feature attribute in Multimedia class should be a #ManyToOne relationship.
By default, join tables are created for the mapping of many-to-many relationships and unidirectional one-to-many relationships.
If you want to avoid the join class, I think you can map the multimedia attribute in Feature class by using the #JoinTable like that:
#OneToMany
#JoinTable(name = "multimedia_feature",
joinColumns = #JoinColumn(name = "feature_oid"),
inverseJoinColumns = #JoinColumn(name = "multimedia_oid") )
private List<Multimedia> multimediaList;
If you do need bidirectional relationship with join table, the mapping will be like this:
public class Feature implements Serializable {
#OneToMany(mappedBy="feature")
private List<Multimedia> multimediaList;
...
}
public class Multimedia implements Serializable {
#ManyToOne
#JoinTable(name = "multimedia_feature",
joinColumns = #JoinColumn(name = "multimedia_oid") ,
inverseJoinColumns = #JoinColumn(name = "feature_oid"))
private Feature feature;
...
}
Or you can totally remove the join table with a bidirectional association by introducing a join column in multimedia table, like feture_oid. So that you can easily map the feature attribute in Multimedia class:
#ManyToOne
#JoinColumn(name = "feature_oid")
private Feature feature;
And in the Feature class will be like:
#OneToMany(mappedBy="feature")
private List<Multimedia> multimediaList;