Spring Data Rest - How to have an association relationship - java

I have two classes, Role and Permission with a ManyToMany relationship between them. My problem is that each relationship has some extra data that comes with it therefore I believe I need to create an intermediary class to store these extra data, so that is the RolePermission class.
This is basically what I have, the parameter and domain are the extra data that are required for each relationship.
Here is the code I have right now for my classes.
Role.java
#Entity
#Table(name = "Sec_Role")
#DynamicUpdate
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String description;
private String name;
// This is another relationship which is working just fine (because there are no intermediary data needed.
#ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER)
private Set<Group> groups;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "role")
private List<RolePermission> permissions = new ArrayList<RolePermission>(0);
...Standard getters and setters and constructor
Permission.java
#Entity
#Table(name = "Sec_Permission")
public class Permission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "permission")
private List<RolePermission> roles = new ArrayList<RolePermission>(0);
...Standard getters and setters and constructor
RolePermission.java
#Entity
#Table(name = "Sec_Role_Permission")
#DynamicUpdate
public class RolePermission {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int domain;
private String parameter;
#Column(updatable = false, insertable = false)
private int role_id;
#Column(updatable = false, insertable = false)
private int permission_id;
#ManyToOne(fetch = FetchType.LAZY )
#JoinColumn(name = "role_id", nullable = false)
private Role role;
#ManyToOne(fetch = FetchType.LAZY )
#JoinColumn(name = "permission_id", nullable = false)
private Permission permission;
...Standard getters and setters and constructor
Every class has a standard Repository like so
#RepositoryRestResource(path = "roles")
public interface RoleRepository extends CrudRepository<Role, Integer> {
}
Now this code works fine for reading data and relationships, my problem is that I cannot figure out what I am suppose to do or what end point to call to add/modify/delete a relationship between Role and Permission.
Currently if I call /roles/11/permissions I will get this back:
{
"_embedded": {
"rolePermissions": []
},
"_links": {
"self": {
"href": "http://localhost:8887/api/v1/roles/11/permissions"
}
}
}
How can I add a permission to this role?
I tried executing a POST request to /roles/11/permissions with the following JSON body and I got a 204 No Content response. This basically means success but then when I do a GET request to /roles/11/permissions I do not see permission with ID 1 there so it did not work.
{
"domain": 0,
"parameter": "Some param",
"role_id": 11,
"permission_id": 1
}

Since your mapping is itself an Entity you can model your API based on the Resource ( in this case RolePermission). Basically when you would want to provide an API to add rolepermission
Some thing like
http://localhost:8887/api/v1/rolepermission
POST
{
"roleid":"xxxxx"
"permissionid":"xxxxxx"
"parameterid":"xxxxxx"
"domain":"xxxxx"
}

Related

Getting Exception while adding new User in Spring Security

I have a user class and role class and user role class . Now every time i am trying to add a new user with a set of role which is already existing it throws a Unique error which is correct . But ideally it should not try to save the new role if it already exists . Below i am adding all my tables and save method .
#Table(name = "t_user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "email")
private String email;
#Column(name = "first_name")
private String firstName;
#Size(max = 100)
#NotBlank(message = "Last name can not be empty")
#Column(name = "last_name")
private String lastName;
#Column(name = "is_archived")
private Boolean isArchived = false;
#Column(name = "qualification")
private String qualification;
#JsonIgnore
#Column(name="password")
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "t_user_role", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles = new HashSet<>();
}
#Data
#Table(name = "m_role")
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="name")
private String name;
}
#Data
#Table(name = "t_user_role")
#Entity
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
method where i am saving the user:
User newUser = new User();
newUser.setFirstName(user.getFirstName());
newUser.setLastName(user.getLastName());
newUser.setEmail(user.getEmail());
newUser.setMobileNumber(user.getPassword());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
newUser.setRoles(user.getRoles());
return userRepository.save(newUser);
}
and below is the post request format to create the user:
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"name":"ADMIN"
}]
}
I do not want to insert the role if present which should be ideal . But this is the standard way i find while implementing spring security with roles
Your request is missing id of the role. As id is not present. Spring try to add a new role in role table.
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"id" : "" // ID must be present here.
"name":"ADMIN"
}]
}
Or from the role -> name, you can fetch Role entity/obj from the role table and set it in User object.
[Update 2]:
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
You need to change the cascade type from 'ALL' to 'DETACH'. See: ALL means if you save USER, ROLE will also get saved, if you delete USER, role should also get delete. This is not what we want. You only need to use 'ROLE', not manipulate the 'ROLE' tables record in any way.
On behalf of what I understand, your requirements are:
User entity
Role entity, with each user having multiple roles
If role is passed from client, you want to save the role only if it does not exist in your database, else you want to use the existing role (UPDATE: which as per comments and my opinion, is never an ideal thing to do)
In your case, I would suggest let Spring take care of the User->Roles relationship as follows:
public class User {
... all fields
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
}
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
In Role repository, you would want a method: Optional<Role> findByName(String Name);
In between the layers (preferably in the service layer), try this:
public Map<String, Object> addUser(User user) {
// perform validations
user.getRoles().forEach(role -> {
Role existing = roleRepository.findByName(role.getName())
.orElse(new Role(role.getName())); // using optional to create new role with name passed in from the client
if (existing.getId() != null) user.setRole(existing); // UPDATE: NOT IDEAL
});
... other tasks
userRepository.save(user); // this all saves the correct role
return yourResponseMap;
}
Other notes:
We generally prefer to keep fetches Lazy, instead of Eager. But there are cases when you may need Eager retrieval so it depends on you.
Letting Spring Data JPA handle third tables is better in terms of convenience in my opinion.
org.hibernate.PersistentObjectException: detached entity passed to persist occurs when you're trying to save the role passed in from client directly without loading it on your application (see the service layer method for 'add user').
Check this link, you might find it helpful.

Error with #ManyToOne - JPA/Hibernate Detached Entity Passed to Persist

Good Evening,
I am relatively new to using Hibernate, and I am running into the following error:
"message": "org.springframework.web.util.NestedServletException: Request processing failed;
nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist:
com.company.project.data.relational.models.ListsItems; nested exception is org.hibernate.PersistentObjectException:
detached entity passed to persist: com.company.project.data.relational.models.ListsItems",
I have a JSON object being sent from the front-end that has a nested object. I am trying to get the the nested items in a separate table in MySQL, with a relationship using the original objects ID.
Here's an example of the JSON:
{
"name":"Test",
"type":"App Id List",
"listItems":
[
{
"id":1,
"name":"Test",
"value":" 1"
},
{
"id":2,
"name":"NEW TEST",
"value":" 2"
}
]
}
Here is my Lists model:
#Entity
#Getter
#Setter
#NoArgsConstructor
#Table(name = "lists")
public class Lists implements Serializable, OperationalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(columnDefinition = "char", nullable = false)
private String guid;
private String name;
private String type;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "listItems", orphanRemoval = true)
#Cascade({org.hibernate.annotations.CascadeType.ALL, })
private Set<ListsItems> listItems;
private Date created;
private Date updated;
}
And here is my ListsItems model:
#Getter
#Setter
#Entity
#Table(name = "lists_items")
#NoArgsConstructor
public class ListsItems implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private String value;
#NaturalId
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#JoinColumn(name = "lists_id", referencedColumnName = "id")
private Lists listItems;
}
Here is the save function:
#PostMapping(value = "/add")
#PreAuthorize("hasRole('ADMIN')")
public #ResponseBody WebResponse<W> create(#RequestBody W webModel) {
D dbModel = asDbModel(webModel);
dbModel.setGuid(UUID.randomUUID().toString());
return WebResponse.success(createWebModelFromDbModel(getDatabaseEntityRepository().save(dbModel)));
}
Any ideas on what might be causing this error? I've searched a bit but nothing I've tried from any other solutions have worked out.
Thanks in advance!
- Travis W.
The answer was to make the following changes to ListItems:
#JsonIgnore // this import will be from jackson
#NaturalId
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "lists_id", referencedColumnName = "id")
private Lists list;
And the following to Lists:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "list", orphanRemoval = true)
#Cascade({org.hibernate.annotations.CascadeType.ALL, })
private Set<ListsItems> listItems;
I also needed to iterate over the results:
#Override
protected Lists asDbModel(WebLists webModel) {
Lists dbModel = new Lists();
dbModel.setId(webModel.getId());
dbModel.setName(webModel.getName());
dbModel.setType(webModel.getType());
dbModel.setListItems(webModel.getListItems());
for(ListsItems item : webModel.getListItems()) {
item.setList(dbModel);
}
return dbModel;
}

I have to persist a Map<EntityType, List<EntityType>> with JPA

I'm trying to persist an Entity that has a Map as one of its values. To be more precise. I have the #Entity Request that have a compound primary key with three elements. This primary key is composed by an id, the User an Map<EntityType, List<EntityType>> where the first EntityType is the selected service and the related value is the list of the items where the service will be applied to.
Below the code that I have but I'm missing the annotation that i have to use for the Map. I read online that the good way to go is the create a wrapper entity like to one that i created (SelectedService2MyItem) that holds the list and then the map is just a key-value pair between two entity but I can't make it works and I don't know how to proceed.
Does anyone can help me?
Request Entity
#Entity
public class Request {
#EmbeddedId
private RequestId id;
#Column
private String name;
#ManyToOne
#JoinColumn(name = "user_id", foreignKey = #ForeignKey(name = "FK_user_id"), nullable=false)
private User user;
//Getter, setter, constructor omitted
}
RequestId
#Embeddable
public class RequestId {
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "user_id", foreignKey = #ForeignKey(name = "FK_user_id"), nullable=false)
private User user;
private Map<ServiceOffered, SelectedService2MyItem> service2MyItem = new HashMap<ServiceOffered, SelectedService2MyItem>();
//Getter, setter, constructor omitted
}
SelectedService2MyItem
#Entity
public class SelectedService2MyItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(mappedBy = "id")
private List<MyItem> myItemsSelected;
//Getter, setter, constructor omitted
}

Hibernate: Delete an entity in network of entities

In my Spring boot app, there are two types of entities: User and Group:
User can own 0 to N groups
Group can have 1 to M members
In the User class there is a list of Group that he/she owns or is a member of, and in the Group class, there is a list of User (i.e. members).
These classes refer to each other using hibernate annotations.
class User {
#ManyToMany(cascade = CascadeType.REFRESH)
private List<Group> groups;
}
class Group {
#ManyToOne(cascade = CascadeType.REFRESH)
#NotNull
#JoinColumn(name="OWNER_ID", referencedColumnName="id")
private User owner;
#ManyToMany
#JoinTable(joinColumns = #JoinColumn(referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(referencedColumnName = "id"))
private List<User> members;
}
In the User service layer there's a Delete method which is supposed to delete a user from the repository. This delete should fire a series of actions: all the groups owned by that user gets deleted, and the deleted groups should be removed from list of groups of their members. All these should be saved to the repository.
If I add other types of entities to this network, this process gets much more complicated.
My question is: Doesn't hibernate handle this automatically ? Should I grab each member and delete the group one by one and save it to the repository ?
CascadeType.REFRESH means Managed objects can be reloaded from the database by using the refresh method.
This will not help you solving your requirement. You need to use “orphanRemoval = true” CascadeType. “orphanRemoval = true” removes an owned object from the database when it’s removed from its owning relationship.
Example:
EmployeeEntity.java
#Entity #Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
private static final long serialVersionUID = -1798070786993154676L;
#Id #Column(name = "ID", unique = true, nullable = false)
private Integer employeeId;
#Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
private String firstName;
#Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
private String lastName;
#OneToMany(orphanRemoval = true, mappedBy = "employee")
private Set<AccountEntity> accounts;
}
AccountEntity.java
#Entity (name = "Account") #Table(name = "Account")
public class AccountEntity implements Serializable
{
private static final long serialVersionUID = 1L;
#Id #Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer accountId;
#Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
private String accountNumber;
#ManyToOne
private EmployeeEntity employee;
}
OR You can use CascadeType.ALL too.
For further reading, go through below link:
CascadeTypes

Ignore an Entity fields for a Spring REST call

I have two Entity classes "Teacher & Class" where there is a #OneToMany relationship between them. The first one has a rest interface at /teachers/{id} and the second one has a rest interface at /classes/{id}. When a user sends a GET request to the Teacher interface, he should receive all of the Teacher and Class fields. But, when the user sends a GET request to the Class interface, I would like him to receive all of the Class fields and only a part of the Teacher fields "firstName, lastName"
Here are the entities code:
#Entity
#DiscriminatorValue("Th")
public class Teacher{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
private String phone;
#Column(unique = true)
#Email
private String email;
private String password;
#OneToMany(mappedBy = "teacher", fetch = FetchType.EAGER)
private List<Class> classes;
#OneToMany(mappedBy = "teacher", fetch = FetchType.EAGER)
private List<Note> notes;
protected Teacher() {
}
}
#Entity
public class Class {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToOne()
#JoinColumn(name = "subject_id")
private Subject subject;
#Enumerated(EnumType.STRING)
private Grade grade;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teacher_id")
private Teacher teacher;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "class_student", joinColumns = { #JoinColumn(name = "class_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "student_id", referencedColumnName = "id") })
private List<Student> students;
protected Class() {
}
}
//getters and setters
Your request sounds mutualy exclusive, don't load Teacher object but do load it for it's ID.
You are better off then making the teacher object lazy load and then initialise it once class is loaded.
FYI
#JsonIgnore prevents loading of objects when RESTfull calls are made.

Categories