Learner
Learner

Reputation: 133

How to fix detached entity passed to persist in spring boot back end and angular front end?

I am implementing a functionality to add users along with one or more existing roles in the database. The Roles are added successfully from angular front end. When I create a new User in the angular front end and add more than one role to this user, I get the Object in the spring boot back end, however it throws an exception related to the jpa save method in the userServiceImpl class.

org.hibernate.PersistentObjectException: detached entity passed to persist: com.app.model.Role

[
    {
        "roleId": 1,
        "name": "USER",
        "description": "Role assigned to regular users who perform regular tasks within the application, such as add or update transactions and companies"
    },
    {
        "roleId": 2,
        "name": "ADMIN",
        "description": "Role assigned to users who can create other users, roles and perform regular users tasks within the Investment application"
    }
]

In the UI (angular front end) I need to create new users and assign them one or more existing roles. I can see from the console that the roles I added are there, however I am not receiving the roles in spring boot backend, only the regular user info such as name, last name, email, password but not the roles.

Here's my User component where I create the users.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { Role } from 'src/app/model/Role';
import { User } from 'src/app/model/User';
import { RoleServiceService } from 'src/app/services/role-service.service';
import { UserServiceService } from 'src/app/services/user-service.service';

@Component({
  selector: 'app-user',
  templateUrl: './user.component.html',
  styleUrls: ['./user.component.css']
})
export class UserComponent implements OnInit {

  user!: User;
  createForm!:FormGroup;
  errorMessage!:string;
  role: Role[]= [];


  constructor(private userService: UserServiceService,
              private router: Router,
              private roleService: RoleServiceService) { }

  ngOnInit(): void {

    this.getAllRoles();

    this.createForm = new FormGroup({
      'userId': new FormControl(),
      'firstName': new FormControl(null, Validators.required),
      'lastName': new FormControl(null, Validators.required),
      'email': new FormControl(null, Validators.required),
      'password': new FormControl(null, Validators.required),
      'roles': new FormControl()
    });
  }


  onSubmit(){

    this.user = new User(

      this.createForm.get('userId')?.value,
      this.createForm.get('firstName')?.value,
      this.createForm.get('lastName')?.value,
      this.createForm.get('email')?.value,
      this.createForm.get('password')?.value,
      this.createForm.get('roles')?.value

    )

    console.log(this.user);

    this.userService.addUser(this.user).subscribe(
      data=>{
        this.router.navigate([""]);
      }, err=>{
        this.errorMessage = "Unable to add user!";
        console.log(err);
      }
    )

  }

  getAllRoles(){
    this.roleService.getAllRoles().subscribe(
      data=>{
        this.role = data;
        console.log(data);
      }
    )
  }

}

In the back end, here's my User service where I add the users. This is working partially as I am not getting the roles from angular.

@Override
public User addUser(User newUser) throws Exception {
    
    List<User> users = userRepo.findAll();
    
    for(User user: users) {
        if(user.getEmail().equalsIgnoreCase(newUser.getEmail())) {
            throw new Exception("User already exists");
        }
    }
    
    Set<Role> roles = newUser.getRoles();
    
    for (Role role : roles) {
        newUser.addRole(role);
    }
    
    return userRepo.save(newUser);
}

My mapping method in the controller is this one:

@PostMapping("/register")
public ResponseEntity<?> addUser(@RequestBody User user) throws Exception{
        
    System.out.println(user);
    return new ResponseEntity<>(userService.addUser(user), HttpStatus.CREATED);
}

The User entity in java:

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false, updatable = false)
    private long userId;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name="user_roles", joinColumns = @JoinColumn(name="user_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name="role_id", referencedColumnName = "id") )
    
    private Set<Role> roles = new HashSet<>();

    public void addRole(Role role) {
        this.roles.add(role);
    }

    public User() {

    }

    public User(String firstName, String lastName, String email, String password, Set<Role> roles) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.password = password;
        this.roles = roles;
    }

    public long getUserId() {
        return userId;
    }

    public void setUserId(long userId) {
        this.userId = userId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    
    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User [userId=" + userId + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email
                + ", password=" + password + ", roles=" + roles + "]";
    }

}

The model in Angular Front end:

import { Role } from "./Role";


export class User{

    constructor(public userId: number,
                public firstName: string,
                public lastName: string,
                public email: string,
                public password:string,
                public roles: Role){

    }
    
}

The Role entity is the following in Java:

    @Entity
@Table(name = "role")
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, updatable = false)
private Integer roleId;

@Column(name = "role_name")
private String name;

@Column(name = "description")
private String description;


public Role() {
}

public Role(String name, String description) {
    super();
    this.name = name;
    this.description = description;
    
}

public int getRoleId() {
    return roleId;
}

public void setRoleId(int roleId) {
    this.roleId = roleId;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}

}

The Role model in Angular front end is:

export class Role{

    constructor(public roleId: number,
                public name: string,
                public description:string){

    }

    
}

I am certain it has be related to how I add the roles in the reactive form as it is an array of roles but I am not sure how to approach that.

Upvotes: 0

Views: 640

Answers (1)

Learner
Learner

Reputation: 133

Thanks for the help on some key issues in my code. I was able to fix it once I understood the following:

Issues:

  1. User Object keys name for Java and Angular. One of the instance variables name was "roles" with s in my User entity in Java, and in angular role entity that same field was called role without the s.

  2. In the spring boot back end (Java). In the user entity I added an addRole method and I was using it inside my userServiceImpl service which was causing the detached entity passed to persist error/exception. To fix this, all I did was simply remove the addRole method from my service and now I am able to create users and add any role I want or multiple roles and all is saved correctly in the database.

The service now looks as follows:

 @Override
public User addUser(User newUser) throws Exception {
    
    List<User> users = userRepo.findAll();
    
    for(User user: users) {
        if(user.getEmail().equalsIgnoreCase(newUser.getEmail())) {
            throw new Exception("User already exists");
        }
    }
    
    return userRepo.save(newUser);
}

Upvotes: 1

Related Questions