使用复合键保存实体获取 ConversionNotSupportedException

Posted

技术标签:

【中文标题】使用复合键保存实体获取 ConversionNotSupportedException【英文标题】:Saving entity with composite key get ConversionNotSupportedException 【发布时间】:2019-01-11 21:51:35 【问题描述】:

我使用 spring boot 2,我的一些实体有复合键

当我尝试保存实体时,出现此错误

转换请求元素失败: org.springframework.beans.ConversionNotSupportedException:失败 将类型“java.lang.Integer”的属性值转换为所需类型 'com.lcm.model.SamplingsPK' 用于属性“采样”;嵌套异常 是 java.lang.IllegalStateException:无法转换类型的值 'java.lang.Integer' 到所需的类型 'com.lcm.model.SamplingsPK' 属性“采样”:未找到匹配的编辑器或转换策略

我用那个方法得到了我的实体

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);

我的实体

@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;
    

编辑

保存样本没问题,当我通过采样时

【问题讨论】:

遇到同样的问题。我发现如果您直接使用 EntityManager,您可以正确地保留实体。同时,我打开了jira.spring.io/browse/DATAJPA-1391 不确定,因为我可以创建采样和采样...但可以通过压缩保存采样... 【参考方案1】:

我也注意到了这一点。它不会发生在我的 Windows IDE 上,但会发生在 Azure 构建服务器上

我在org.springframework.data:spring-data-jpa:jar:2.4.5:compile

我将 BOM 升级到 &lt;spring-data-bom.version&gt;2020.0.15&lt;/spring-data-bom.version&gt;,所以我有 org.springframework.data:spring-data-jpa:jar:2.4.15:compile

一旦我这样做了,它就开始正常工作了。

【讨论】:

【参考方案2】:

问题在于,由于 ID 是手动设置的,并且这些实体上没有 @Version 属性,因此 Spring Data 无法知道实体是全新的还是现有的。在这种情况下,它决定它是一个现有实体并尝试使用merge 而不是persist。这显然是一个错误的结论。

您可以阅读更多有关 Spring Data 如何确定实体是否为新实体的信息here。

我发现的最佳解决方案是始终让具有手动设置 ID 的实体类实现 Persistable interface。这解决了问题。对于任何此类情况,我都会为自己制定这条规则。大多数时候我不必实现Persistable,因为我的实体要么有一个自动生成的密钥,要么我的实体使用“@Version”注释。但这是特例。

因此,根据 Spring 官方文档中的建议,例如 Samplings 类将变为:

@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);
    

【讨论】:

【参考方案3】:

此问题在 https://jira.spring.io/browse/DATAJPA-1391 进行跟踪,并且与在 Samples 中使用 @Id @ManyToOne 有关。作为一种解决方法,您可以尝试为 Samplings 创建一个构造函数,该构造函数接受它的两个主键,或者一个接受 java.lang.Integer 的主键?这适用于单级复合主键,但如果您有多个级别,它可能不起作用。

SamplingsPK 中的 year 也输入为 int 而不是 Integer。这可能会导致 PK 识别出现问题,因为处理可自动装箱的原始类需要特别考虑,我怀疑是否考虑过。

【讨论】:

以上是关于使用复合键保存实体获取 ConversionNotSupportedException的主要内容,如果未能解决你的问题,请参考以下文章

使用实体的复合键作为另一个实体中的 ManyToMany 键

Spring Boot:如何使用复合键创建实体

实体框架代码优先迁移忽略 [Key] 并强制复合键

无法使用 Nhibernate 的 Linq 检索 Group By 实体或复合键

实体框架存在部分自动生成的复合键问题

Doctrine ORM:使用由外键组成的复合主键持久化集合