Search code examples
javaspringspring-bootspring-data-jpaone-to-many

Spring data jpa collection don`t update


Hi I have 2 entities: Department and User.

I find both entities and want to add one to the other But if I add a user to the department, the user has Department = null. And if the opposite is true, then List = null. How to do this correctly?

@Entity
@Table(name = "department")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Department {

    @Id
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2")
    @Column(length = 36, nullable = false, updatable = false)
    private UUID id;


    @Column(name = "name", nullable = false)
    private String name;
    @Column(name = "description", nullable = false)
    private String description;
    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    private List<User> users = new ArrayList<>();

}

@Entity
@Table(name = "user")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2")
    @Column(length = 36, nullable = false, updatable = false)
    private UUID id;

    private String firstName;
    private String lastName;
    private Integer age;


    @ManyToOne
    private Department department;

    @ManyToMany
    @JoinTable(
            name = "device_devices",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "device_id"))
    Set<Device> devices = new HashSet<>();

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equal(id, user.id);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(id);
    }
}

And service

@Override
@Transactional
public void addUserToDepartment(UUID departmentId,UUID userId){
    Department department = departmentRepository.findById(departmentId).orElseThrow(DepartmentNotFoundException::new);
    User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);
    department.getUsers().add(user);
    //user.setDepartment(department);
}

It seems to me that adding user to department and simultaneously adding department to user is not correct. They have to pull themselves up. Help Me


Solution

  • That is why you have mappedBy attribute. It hints to hibernate what side will be responsible for those mappings when you have a bi-directional ascosiation.

    So considering your configuration

    public class Department {
     @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
        private List<User> users = new ArrayList<>();
    
        ...
        }
    

    Then setting the mapping only on User on field department would be enough

    @Override
    @Transactional
    public void addUserToDepartment(UUID departmentId,UUID userId){
        Department department = departmentRepository.findById(departmentId).orElseThrow(DepartmentNotFoundException::new);
        User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);
        //department.getUsers().add(user); You don't need this.
        user.setDepartment(department); This here is enough for hibernate!
    }
    

    BUT

    as pointed out by hibernate documentation in order to have normal java usage inside a method you need to manually update both sides. This means that if you update only 1 side then the changes will be persisted on DB level but on java level your other instance will not be synced with those changes.

    Check the following examples on hibernate documentation (Example 297, Example 298) Hibernate Bi directional mapping normal java usage