Update ManyToMany with JSON

'm trying to Update an entity which has a subset joines by @ManyToMany annotation.

Here is my entity

@Entity
public class Member extends AbstractDefaultEntity implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    @NotEmpty(message = "Please enter name")
    private String name;
    @NotEmpty(message = "Please enter firstname")
    private String firstname;
    @NotEmpty(message = "Please enter street")
    private String street;
    @NotEmpty(message = "Please enter house number")
    private String houseno;
    @NotEmpty(message = "Please enter zip")
    private String zip;
    @NotEmpty(message = "Please enter city")
    private String city;
    private String phone;
    private String mobile;
    private String email;
    private Date birthDate;
    private Date entryDate;
    private Date exitDate;
    private byte active;
    private String idcardnumber;
    private String remarks;

    @JsonManagedReference(value="member-department")
    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @ManyToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL)
    @OrderBy("title ASC")
    @JoinTable(
            name = "DepartmentMember",
            joinColumns = { @JoinColumn(name = "memberid") },
            inverseJoinColumns = { @JoinColumn(name = "departmentid") }
    )
    private Set<Department> departments;
}

And the mapped entities:

@Entity
public class Department extends AbstractDefaultEntity implements java.io.Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @NotNull
    @ManyToOne(cascade = { CascadeType.ALL })
    @JsonBackReference(value="department-leader")
    private Member leader;

    @NotEmpty(message = "Please enter title")
    private String title;

    @ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
    @JoinTable(
            name = "DepartmentMember",
            joinColumns = { @JoinColumn(name = "departmentid") },
            inverseJoinColumns = { @JoinColumn(name = "memberid") }
    )
    @JsonBackReference(value="department-member")


    private Set<Member> members;
}

The reference:

public class DepartmentMember implements java.io.Serializable {

    private GroupMemberId id;
    private Department department;

    private Member member;
}

The DTO

public class MemberDto extends AbstractDefaultDto {
    private Integer id;

    @ExcelProperty(index = 0, i18nProperty = "member.name")
    private String name;

    @ExcelProperty(index = 1, i18nProperty = "member.firstname")
    private String firstname;

    @ExcelProperty(index = 2, i18nProperty = "member.street")
    private String street;

    @ExcelProperty(index = 3, i18nProperty = "member.houseno")
    private String houseno;

    @ExcelProperty(index = 4, i18nProperty = "member.zip")
    private String zip;

    @ExcelProperty(index = 5, i18nProperty = "member.city")
    private String city;

    @ExcelProperty(index = 6, i18nProperty = "member.phone")
    private String phone;

    @ExcelProperty(index = 7, i18nProperty = "member.mobile")
    private String mobile;

    @ExcelProperty(index = 8, i18nProperty = "member.email")
    private String email;

    @ExcelProperty(index = 9, i18nProperty = "member.birthdate")
    private String birthDate;
    private String entryDate;
    private String exitDate;
    private String active;
    private String idcardnumber;
    private String remarks;

Now I’m trying to update a new Member with his departments. When I send this json to the server

{
    "active": "true",
    "birthDate": "1985-07-02",
    "city": "Musterdorf",
    "departments": [2],
    "email": "mail@example.com",
    "entryDate": "2021-04-01",
    "exitDate": "2021-04-30",
    "firstname": "Max",
    "houseno": "812",
    "id": "1",
    "idcardnumber": "123474j",
    "mobile": "114110/411",
    "name": "Mustermann",
    "phone": "4114/41552",
    "remarks": "",
    "street": "Nortweg.",
    "zip": "85224"
}

I get the error messages:

darf nicht null sein (path = Department.leader, invalidValue = null)
Please enter title (path = Department.title, invalidValue = null)

Which should be thrown if I add a new department, but not when referencing the department during an update. How can I mark the properties to be checked only during a save command?

I don’t know how you are de-serializing this JSON, but since you use CascadeType.ALL and I assume Jackson just creates a new instance, the entity is assumed to be new so it tries to persist it. If you don’t want this, you have to load the department or get a handle to a proxy through EntityManager.getReference(Department.class, id) and add that object to the collection.

You might like what Blaze-Persistence Entity Views offers here as it will take care of this and many other issues.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

    @EntityView(Member.class)
    @UpdatableEntityView
    public interface MemberUpdateDto {
        @IdMapping
        Integer getId();
        String getName();
        void setName(String name);
        boolean isActive();
        void setActive(boolean active);
        String getFirtname();
        void setFirstname(String firstname);
        // Other getters/setters for attributes that you want to support...
        Set<DepartmentDto> getDepartments();
        void setDepartments(Set<DepartmentDto> departments);

        @EntityView(Department.class)
        interface DepartmentDto {
            @IdMapping
            Integer getId();
        }
    }

With the Jackon + JAX-RS/Spring Mvc integration you can use it like your regular DTO:

    @RequestMapping(path = "/members", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<String> updateCat(@RequestBody MemberUpdateDto member) {
        memberRepository.save(member);
        return ResponseEntity.ok(member.getId().toString());
    }

The best part is, it will not fetch any database state at all and directly run update/insert/delete statements.