简体   繁体   中英

Spring Data REST call to `save` receiving object with null variable

I'm continuing the development of a system that uses the React JavaScript library (and related stuff) on the front end and Spring Data REST, Hibernate, PostgreSQL and related stuff on the back end.

This system will be used by people who may own one or more companies and their clients. This means that most/all model objects will have a reference to the Company (ies) that they belong to. Also, company owners will have a few Employee s that will have higher level access on this system (or these will be the owners themselves).

I need to implement a functionality where, when a company is inserted in the database, an employee is inserted as well. Also, if one fails, both must fail. Because of how the model was set up, I'm sending an Employee object to be saved, and, within it, the new Company , like this (using Axios):

employee: {
    // ...,
    company: {
        // ....
    }
}

Problem is, when the save method is called in the back end, the Company member of the Employee object is null . I've tried a few things, like messing with the relationship, adding an Employee list to the Company object, passing the Company object separately, but nothing worked.

What else could I try? Here are some classes:

Record.java

package xxx.model.common;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotNull;

import lombok.Data;

@Data
@MappedSuperclass
public abstract class Record {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Long id;

    @NotNull
    @Column(name = "deleted")
    protected Boolean isDeleted = false;

    @NotNull
    @Column(name = "enabled")
    protected Boolean isEnabled = true;
}

Company.java

package xxx.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;

import com.fasterxml.jackson.annotation.JsonManagedReference;
import xxx.common.Record;
// ...

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper=false)
@Entity
@Table(name="company")
@AttributeOverrides( { @AttributeOverride(name = "id", column = @Column(name = "id_company")) } )
public class Company extends Record {

    /*
     * ...
     */

    // Necessary for Hibernate
    protected Company() {}

    public Company(/* ... */) {
        /*
         * ...
         */
    }
}

Registry.java

package xxx.model.common;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.validation.constraints.NotBlank;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper=false)
@MappedSuperclass
public abstract class Registry extends Record {

    @NotBlank
    @Column(name = "code", length = 15)
    protected String code;

    @NotBlank
    @Column(name = "name", length = 40)
    protected String name;
}

RegistrySingleCompany.java

package xxx.model.common;

import javax.persistence.CascadeType;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;

import com.fasterxml.jackson.annotation.JsonBackReference;
import xxx.model.Company;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper=false)
@MappedSuperclass
public class RegistrySingleCompany extends Registry {

    @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE }, optional= false)
    @JoinColumn(name="id_company")
    protected Company company;
}

Employee.java

package xxx.model;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import xxx.model.common.RegistrySingleCompany;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@EqualsAndHashCode(callSuper=false)
@Entity
@Table(name="employee")
@AttributeOverrides( { @AttributeOverride(name = "id", column = @Column(name = "id_employee")) } )
public class Employee extends RegistrySingleCompany {

    /*
     * ...
     */

    // Necessary for Hibernate
    protected Employee() {}
}

EmployeeRepositoryCustom.java

package xxx.repository.custom;

import org.springframework.data.repository.query.Param;

import xxx.model.Employee;

public interface EmployeeRepositoryCustom {

    <S extends Employee> S save(S entity);
}

EmployeeRepositoryCustomImpl.java

package xxx.repository.custom;

import javax.persistence.PersistenceContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestBody;

import xxx.model.Employee;

public class EmployeeRepositoryCustomImpl implements EmployeeRepositoryCustom {

    @Override
    @Transactional
    public <S extends Employee> S save(@RequestBody S entity) {
        /*
         * ...
         */
        return entity;
    }
}

EmployeeProjection.java

package xxx.model.projection;

import org.springframework.data.rest.core.config.Projection;

import xxx.model.Employee;

@Projection(name = "employeeProjection", types = { Employee.class }) 
public interface EmployeeProjection {

    Boolean getIsDeleted();

    Boolean getIsEnabled();

    String getCode();

    String getName();

    /*
     * ...
     */
}

EmployeeRepository.java

package xxx.repository;

import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import xxx.model.Employee;
import xxx.model.projection.EmployeeProjection;
import xxx.repository.custom.EmployeeRepositoryCustom;

@RepositoryRestResource(collectionResourceRel = "employee", path = "employees", excerptProjection = EmployeeProjection.class)
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>, EmployeeRepositoryCustom {}

Thanks in advance.

Edit: added missing classes.

As mentioned before, one of the things I tried was to add an Employee list inside the Company object, which implies using Company 's repository instead of the Employee 's one to save both objects, but the other object was also arriving null. However, my colleague found out that, by using exported = false inside @RepositoryRestResource() , the value would be received correctly.

That would screw other things up, so we found the following temporary solution:

  • Create an exported = false repository ( EmployeeWrapper ) for the sole purpose of delivering the necessary Employee data to construct a new one inside save .
  • Instead of adding an Employee list inside Company , add an EmployeeWrapper list.
  • EmployeeWrapper also references Company .

We're still working on a more correct approach.

Update: a more correct approach:

My colleague also found out that, by adding a @Transient Employee list to Company , it's possible to receive the correctly filled out Employee object to save it. I don't know if it works at the repository since, due to other constraints, we moved to use a @RepositoryRestController and are receiving the Company as @RequestBody org.springframework.hateoas.Resource<Company> resource .

We still want to find a better solution, because an Employee list inside Company wasn't planned in our model and, worse yet, we're needing to use list of other things for other methods.

Update: an even better approach:

Experimenting a little more, we created a POJO containing the entities that we needed and received that in the controller, same way as before. Works well.

We're still not satisfied, though. Ideally, we want to receive the Employee to be saved, with the Company to be saved inside it, and save them both at once.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM