Spring-Data-Jpa clearing all parameters of linked entities after persist

geoffreydv :

I'm trying to use JPA (with Hibernate) to save 2 entities. Spring data is providing the interface but I don't think it matters here.

I have a main entity called 'Model'. This model has many 'Parameter' entities linked. I'm writing a method to save a model and its parameters.

This is the method:

  private void cascadeSave(Model model) {

    modelRepository.save(model);

    for (ParameterValue value : model.getParameterValues()) {
        parameterValueRepository.save(value);
    }
}

This is the problem:

When I load a Model that already existed before, add some new parameters to it and then call this method to save both of them something strange happens:

Before the first save (modelRepository.save) this is what the model's data looks like when debugging:

enter image description here

The model has 2 parameters, with filled in values (name and model are filled).

Now, after saving the model the first save in my method, this happens. Note that the object reference is a different one so Hibernate must have done something magical and recreated the values instead of leaving them alone:

enter image description here

For some reason hibernate cleared all the attributes of the parameters in the set.

Now when the saving of the new parameters happens in the following code it fails because of not null constraints etc.

My question: Why does hibernate clear all of the fields?

Here are the relevant mappings:

ParameterValue

@Entity
@Table(name = "tbl_parameter_value")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "PARAMETER_TYPE")
public abstract class ParameterValue extends AbstractBaseObject {

    @Column(nullable = false)
    @NotBlank
    private String name;
    private String stringValue;
    private Double doubleValue;
    private Integer intValue;
    private Boolean booleanValue;
    @Enumerated(EnumType.STRING)
    private ModelType modelParameterType;
    @Column(precision = 7, scale = 6)
    private BigDecimal bigDecimalValue;
    @Lob
    private byte[] blobValue;

    ParameterValue() {
    }

    ParameterValue(String name) {
        this.name = name;
    }

ModelParameterValue

@Entity
@DiscriminatorValue(value = "MODEL")
public class ModelParameterValue extends ParameterValue {

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "model_id", foreignKey = @ForeignKey(name = "FK_VALUE_MODEL"))
    private Model model;

    ModelParameterValue() {
        super();
    }

    ModelParameterValue(String name) {
        super(name);
    }

Model

@Entity
@Table(name = "tbl_model")
public class Model extends AbstractBaseObject implements Auditable {

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "model")
    private Set<ModelParameterValue> parameterValues = new HashSet<>();

EDIT

I was able to reproduce this with a minimal example. If you replace everything spring data does this is what happened under the hood (em is a JPA EntityManager):

public Model simpleTest() {

    Model model = new Model("My Test Model");
    em.persist(model);

    model.addParameter(new Parameter("Param 1"));
    em.merge(model);

    for (Parameter child : model.getParameters()) {
        em.persist(child);
    }

    return model;
}

When the merge is executed, all of the attributes of the parameters are set to null. They are actually just replaced with completely new parameters.

Emil Hotkowski :

I guess you are using Spring Data Jpa as your modelRepository. This indicates following consequences.

Spring Repository Save

S save(S entity)

Saves a given entity. Use the returned instance for further operations as the save operation might have changed the entity instance completely.

So it is normal behaviour which you had encountered.

Code should be changed to :

model = modelRepository.save(model);

for (ParameterValue value : model.getParameterValues()) {
    parameterValueRepository.save(value);
}

EDIT:

I think that your saving function is broken in sense, that you do it backwards. Either you can use CascadeType on your relation or you have to save children first.

Cascade

Cascade works like that "If you save Parent, save Children, if you update Parent, update Children ..."

So we can put cascade on your relation like that :

@Entity
@Table(name = "tbl_model")
public class Model extends AbstractBaseObject implements Auditable {

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "model", cascade = CascadeType.ALL)
    private Set<ModelParameterValue> parameterValues = new HashSet<>();

and then only save like this

  private void cascadeSave(Model model) {

    modelRepository.save(model);
    //ParamValues will be saved/updated automaticlly if your model has changed

}

Bottom-Up save

Second option is just to save params first and then model with them.

  private void cascadeSave(Model model) {

    model.setParameterValues(
      model.getParameterValues().stream()
       .map(param ->  parameterValueRepository.save(param))
       .collect(Collectors.toSet())
   );
    modelRepository.save(model);
}

I haven't checked second code in my compiler but the idea is to first save children (ParamValues), put it into Model and then save Model : )

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=91381&siteId=1