robert trudel
robert trudel

Reputation: 5749

Saving entity with composite key get ConversionNotSupportedException

I use spring boot 2 and some of my entities have composite key

When I try to save an entity, I get this error

Failed to convert request element: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.Integer' to required type 'com.lcm.model.SamplingsPK' for property 'sampling'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.Integer' to required type 'com.lcm.model.SamplingsPK' for property 'sampling': no matching editors or conversion strategy found

I get my entity with that method

public Samples findById(Integer id, int year, String sampleLetter) {
    Optional<Samples> optSamples = samplesRepository.findById(new SamplesPK(new SamplingsPK(year, id), sampleLetter));

    if (optSamples.isPresent()) {
        return optSamples.get();
    }

    return null;
}


Samples samples = samplesService.findById(idSeq, year, samplesLetter);

Compressions compressionTest = null;

if (samples.getTestSamples().getAbsorptionTest() != null) {
    compressionTest = samples.getTestSamples().getCompressionTest();
} else {
    compressionTest = new Compressions();
}

samplesService.save(samples);

My entity

@Entity
@IdClass(SamplesPK.class)
public class Samples extends BaseEntity{
    @Id
    private String sampleLetter;

    @Embedded
    private TestSamples testSamples;

    @Id
    @ManyToOne(optional=false)
    @JoinColumns({
        @JoinColumn(name = "sampling_id", referencedColumnName = "id"),
        @JoinColumn(name = "sampling_year", referencedColumnName = "year")})
    private Samplings sampling;
}

@Entity
@IdClass(SamplingsPK.class)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
    @Id
    private Integer year;

    @Id
    @GeneratedValue
    private Integer id;

    @OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Samples> samples = new ArrayList<>();
}

public class SamplingsPK implements Serializable {

    private int year;

    private Integer id;

    public SamplingsPK(int year, Integer id) {
        this.id = id;
        this.year = year;
    }
}

public class SamplesPK implements Serializable {

    private SamplingsPK sampling;

    private String sampleLetter;

    public SamplesPK(SamplingsPK sampling, String sampleLetter) {
        this.sampling = sampling;
        this.sampleLetter = sampleLetter;
    }
}

edit

no problem to save sample, when I pass from sampling

Upvotes: 12

Views: 2337

Answers (3)

Archimedes Trajano
Archimedes Trajano

Reputation: 41240

I noticed this too. It does not happen on my IDE on Windows but it happens on the Azure build server

I was on org.springframework.data:spring-data-jpa:jar:2.4.5:compile.

I upgraded the BOM to <spring-data-bom.version>2020.0.15</spring-data-bom.version> so I have org.springframework.data:spring-data-jpa:jar:2.4.15:compile

Once I did that it started working correctly.

Upvotes: 0

peterh
peterh

Reputation: 19225

The problem is that since the IDs are set manually and there's no @Version property on these entities then Spring Data has no good way of knowing if the entity is a brand new one or an existing one. In this case it decides it is an existing entity and attempts a merge instead of a persist. This is obviously a wrong conclusion.

You can read more about how Spring Data decides if an entity is new or not here.

The best solution I've found is to always let entity classes with manually set IDs implement Persistable interface. This solves the problem. I make this a rule for myself for any such case. Most of the time I do not have to implement Persistable because my entity either has an auto-generated key or my entity uses a "@Version" annotation. But this is special case.

So, as per the recommendation in the Spring official documentation, for example the Samplings class would become:

@Entity
@IdClass(SamplingsPK.class)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings implements Persistable<SamplingsPK> {
    @Transient
    private boolean isNew = true; 

    @Id
    private Integer year;

    @Id
    @GeneratedValue
    private Integer id;

    @OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Samples> samples = new ArrayList<>();

    @Override
    public boolean isNew() {
        return isNew; 
    }

    @PrePersist 
    @PostLoad
    void markNotNew() {
        this.isNew = false;
    }

    @Override
    public SamplingsPK getId() {
        return new SamplingsPK(year, id);
    }
}

Upvotes: 7

Mitchell Skaggs
Mitchell Skaggs

Reputation: 360

This issue is tracked at https://jira.spring.io/browse/DATAJPA-1391 and has to do with the use of @Id @ManyToOne inside of Samples. As a workaround, you can try creating a constructor for Samplings that takes in its two primary keys, or maybe one that takes a java.lang.Integer? That's what worked for a single level of composite primary keys, but it might not work if you have multiple levels.

You also have year in SamplingsPK typed as an int rather than an Integer. This may cause problems with PK recognition, since special consideration is needed to handle autobox-able primitive classes and I doubt it was considered.

Upvotes: 0

Related Questions