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:
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;
}
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(/* ... */) {
/*
* ...
*/
}
}
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;
}
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;
}
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() {}
}
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);
}
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;
}
}
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();
/*
* ...
*/
}
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:
exported = false
repository ( EmployeeWrapper
) for the sole purpose of delivering the necessary Employee
data to construct a new one inside save
. 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.